DAViCal
iSchedule.php
1 <?php
17 require_once("XMLDocument.php");
18 
25 class iSchedule
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 $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' );
50 
51  function __construct ( )
52  {
53  global $c;
54  $this->selector = 'cal';
55  if ( is_object ( $c ) && isset ( $c->scheduling_dkim_selector ) )
56  {
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 ) )
61  {
62  $key = file_get_contents ( $this->schedule_private_key );
63  if ( $key !== false )
64  $this->schedule_private_key = $key;
65  }
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;
72  }
73  }
74 
78  function getTxt ()
79  {
80  global $icfg;
81  // TODO handle parents of subdomains and procuration records
82  if ( $icfg [ $this->remote_selector . '._domainkey.' . $this->remote_server ] )
83  {
84  $this->dk = $icfg [ $this->remote_selector . '._domainkey.' . $this->remote_server ];
85  return true;
86  }
87 
88  $dkim = dns_get_record ( $this->remote_selector . '._domainkey.' . $this->remote_server , DNS_TXT );
89  if ( count ( $dkim ) > 0 )
90  {
91  $this->dk = $dkim [ 0 ] [ 'txt' ];
92  if ( $dkim [ 0 ] [ 'entries' ] )
93  {
94  $this->dk = '';
95  foreach ( $dkim [ 0 ] [ 'entries' ] as $v )
96  {
97  $this->dk .= trim ( $v );
98  }
99  }
100  dbg_error_log( 'ischedule', 'getTxt '. $this->dk . ' XX');
101  }
102  else
103  {
104  dbg_error_log( 'ischedule', 'getTxt FAILED '. print_r ( $dkim ) );
105  $this->failed = true;
106  return false;
107  }
108  return true;
109  }
110 
114  function setTxt ( $dk )
115  {
116  $this->dk = $dk;
117  }
118 
122  function parseTxt ( )
123  {
124  if ( $this->failed == true )
125  return false;
126  $clean = preg_replace ( '/\s?([;=])\s?/', '$1', $this->dk );
127  $pairs = preg_split ( '/;/', $clean );
128  $this->parsed = array();
129  foreach ( $pairs as $v )
130  {
131  list($key,$value) = preg_split ( '/=/', $v, 2 );
132  $value = trim ( $value, '\\' );
133  if ( preg_match ( '/(g|k|n|p|s|t|v)/', $key ) )
134  $this->parsed [ $key ] = $value;
135  else
136  $this->parsed_ignored [ $key ] = $value;
137  }
138  return true;
139  }
140 
144  function validateKey ( )
145  {
146  $this->failed = true;
147  if ( isset ( $this->parsed [ 's' ] ) )
148  {
149  if ( ! preg_match ( '/(\*|calendar)/', $this->parsed [ 's' ] ) ) {
150  dbg_error_log( 'ischedule', 'validateKey ERROR: bad selector' );
151  return false; // not a wildcard or calendar key
152  }
153  }
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' ] );
156  return false; // we only speak rsa for now
157  }
158  if ( isset ( $this->parsed [ 't' ] ) && ! preg_match ( '/^[y:s]+$/', $this->parsed [ 't' ] ) ) {
159  dbg_error_log( 'ischedule', 'validateKey ERROR: type mismatch' );
160  return false;
161  }
162  else
163  {
164  if ( preg_match ( '/y/', $this->parsed [ 't' ] ) )
165  $this->failOnError = false;
166  if ( preg_match ( '/s/', $this->parsed [ 't' ] ) )
167  $this->subdomainsOK = false;
168  }
169  if ( isset ( $this->parsed [ 'g' ] ) )
170  $this->remote_user_rule = $this->parsed [ 'g' ];
171  else
172  $this->remote_user_rule = '*';
173  if ( isset ( $this->parsed [ 'p' ] ) )
174  {
175  if ( preg_match ( '/[^A-Za-z0-9_=+\/]/', $this->parsed [ 'p' ] ) )
176  return false;
177  $data = "-----BEGIN PUBLIC KEY-----\n" . implode ("\n",str_split ( $this->parsed [ 'p' ], 64 )) . "\n-----END PUBLIC KEY-----";
178  if ( $data === false )
179  return false;
180  $this->remote_public_key = $data;
181  }
182  else {
183  dbg_error_log( 'ischedule', 'validateKey ERROR: no key in dns record' . $this->parsed [ 'p' ] );
184  return false;
185  }
186  $this->failed = false;
187  return true;
188  }
189 
193  function getServer ( )
194  {
195  global $icfg;
196  if ( $icfg [ $this->domain ] )
197  {
198  $this->remote_server = $icfg [ $this->domain ] [ 'server' ];
199  $this->remote_port = $icfg [ $this->domain ] [ 'port' ];
200  $this->remote_ssl = $icfg [ $this->domain ] [ 'ssl' ];
201  return true;
202  }
203  $this->remote_ssl = false;
204  $parts = explode ( '.', $this->domain );
205  $tld = $parts [ count ( $parts ) - 1 ];
206  $len = 2;
207  if ( strlen ( $tld ) == 2 && in_array ( $tld, Array ( 'uk', 'nz' ) ) )
208  $len = 3; // some country code tlds should have 3 components
209  if ( $this->domain == 'mycaldav' || $this->domain == 'altcaldav' )
210  $len = 1;
211  while ( count ( $parts ) >= $len )
212  {
213  $r = dns_get_record ( '_ischedules._tcp.' . implode ( '.', $parts ) , DNS_SRV );
214  if ( 0 < count ( $r ) )
215  {
216  $remote_server = $r [ 0 ] [ 'target' ];
217  $remote_port = $r [ 0 ] [ 'port' ];
218  $this->remote_ssl = true;
219  break;
220  }
221  if ( ! isset ( $remote_server ) )
222  {
223  $r = dns_get_record ( '_ischedule._tcp.' . implode ( '.', $parts ) , DNS_SRV );
224  if ( 0 < count ( $r ) )
225  {
226  $remote_server = $r [ 0 ] [ 'target' ];
227  $remote_port = $r [ 0 ] [ 'port' ];
228  break;
229  }
230  }
231  array_shift ( $parts );
232  }
233  if ( ! isset ( $remote_server ) )
234  {
235  if ( $this->try_anyway == true )
236  {
237  if ( ! isset ( $remote_server ) )
238  $remote_server = $this->domain;
239  if ( ! isset ( $remote_port ) )
240  $remote_port = 80;
241  }
242  else {
243  dbg_error_log('ischedule', 'Domain %s did not have srv records for iSchedule', $this->domain );
244  return false;
245  }
246  }
247  dbg_error_log('ischedule', $this->domain . ' found srv records for ' . $remote_server . ':' . $remote_port );
248  $this->remote_server = $remote_server;
249  $this->remote_port = $remote_port;
250  return true;
251  }
252 
256  function getCapabilities ( $domain = null )
257  {
258  if ( $domain != null && $this->domain != $domain )
259  $this->domain = $domain;
260  if ( ! isset ( $this->remote_server ) && isset ( $this->domain ) && ! $this->getServer ( ) )
261  return false;
262  $this->remote_url = 'http'. ( $this->remote_ssl ? 's' : '' ) . '://' .
263  $this->remote_server . ':' . $this->remote_port . '/.well-known/ischedule';
264  $remote_capabilities = file_get_contents ( $this->remote_url . '?query=capabilities' );
265  if ( $remote_capabilities === false )
266  return false;
267  $xml_parser = xml_parser_create_ns('UTF-8');
268  $this->xml_tags = array();
269  xml_parser_set_option ( $xml_parser, XML_OPTION_SKIP_WHITE, 1 );
270  xml_parser_set_option ( $xml_parser, XML_OPTION_CASE_FOLDING, 0 );
271  $rc = xml_parse_into_struct( $xml_parser, $remote_capabilities, $this->xml_tags );
272  if ( $rc == false ) {
273  dbg_error_log( 'ERROR', 'XML parsing error: %s at line %d, column %d',
274  xml_error_string(xml_get_error_code($xml_parser)),
275  xml_get_current_line_number($xml_parser), xml_get_current_column_number($xml_parser) );
276  dbg_error_log('ischedule', $this->domain . ' iSchedule error parsing remote xml' );
277  return false;
278  }
279  xml_parser_free($xml_parser);
280  $xmltree = BuildXMLTree( $this->xml_tags );
281  if ( !is_object($xmltree) ) {
282  dbg_error_log('ischedule', $this->domain . ' iSchedule error in remote xml' );
283  $request->DoResponse( 406, translate("REPORT body is not valid XML data!") );
284  return false;
285  }
286  dbg_error_log('ischedule', $this->domain . ' got capabilites' );
287  $this->capabilities_xml = $xmltree;
288  return true;
289  }
290 
294  function queryCapabilities ( $capability, $domain = null )
295  {
296  if ( ! isset ( $this->capabilities_xml ) )
297  {
298  dbg_error_log('ischedule', $this->domain . ' capabilities not set, quering for capability:' . $capability );
299  if ( $domain == null )
300  return false;
301  if ( $this->domain != $domain )
302  $this->domain = $domain;
303  if ( ! $this->getCapabilities ( ) )
304  return false;
305  }
306  switch ( $capability )
307  {
308  case 'VEVENT':
309  case 'VFREEBUSY':
310  case 'VTODO':
311  $comp = $this->capabilities_xml->GetPath ( 'urn:ietf:params:xml:ns:ischedule:supported-scheduling-message-set/urn:ietf:params:xml:ns:ischedule:comp' );
312  foreach ( $comp as $c )
313  {
314  if ( $c->GetAttribute ( 'name' ) == $capability )
315  return true;
316  }
317  return false;
318  case 'VFREEBUSY/REQUEST':
319  case 'VTODO/ADD':
320  case 'VTODO/REQUEST':
321  case 'VTODO/REPLY':
322  case 'VTODO/CANCEL':
323  case 'VEVENT/ADD':
324  case 'VEVENT/REQUEST':
325  case 'VEVENT/REPLY':
326  case 'VEVENT/CANCEL':
327  case 'VEVENT/PUBLISH':
328  case 'VEVENT/COUNTER':
329  case 'VEVENT/DECLINECOUNTER':
330  dbg_error_log('ischedule', $this->domain . ' xml query' );
331  $comp = $this->capabilities_xml->GetPath ( 'urn:ietf:params:xml:ns:ischedule:supported-scheduling-message-set/urn:ietf:params:xml:ns:ischedule:comp' );
332  list ( $component, $method ) = explode ( '/', $capability );
333  dbg_error_log('ischedule', $this->domain . ' quering for capability:' . count ( $comp ) . ' ' . $component );
334  foreach ( $comp as $c )
335  {
336  dbg_error_log('ischedule', $this->domain . ' quering for capability:' . $c->GetAttribute ( 'name' ) . ' == ' . $component );
337  if ( $c->GetAttribute ( 'name' ) == $component )
338  {
339  $methods = $c->GetElements ( 'urn:ietf:params:xml:ns:ischedule:method' );
340  if ( count ( $methods ) == 0 )
341  return true; // seems like we should accept everything if there are no children
342  foreach ( $methods as $m )
343  {
344  if ( $m->GetAttribute ( 'name' ) == $method )
345  return true;
346  }
347  }
348  }
349  return false;
350  default:
351  return false;
352  }
353  }
354 
361  function signDKIM ( $headers, $body )
362  {
363  if ( $this->scheduling_dkim_domain == null )
364  return false;
365  $b = '';
366  if ( is_array ( $headers ) !== true )
367  return false;
368  foreach ( $headers as $key => $value )
369  {
370  $b .= $key . ': ' . $value . "\r\n";
371  }
372  $dk['v'] = '1';
373  $dk['a'] = 'rsa-' . $this->scheduling_dkim_algo;
374  $dk['s'] = $this->selector;
375  $dk['d'] = $this->scheduling_dkim_domain;
376  $dk['c'] = 'simple-http'; // implied canonicalization of simple-http/simple from rfc4871 Section-3.5
377  if ( isset ( $_SERVER['SERVER_NAME'] ) && strstr ( $_SERVER['SERVER_NAME'], $this->domain ) !== false ) // don't use when testing
378  $dk['i'] = '@' . $_SERVER['SERVER_NAME']; //optional
379  $dk['q'] = 'dns/txt'; // optional, dns/txt is the default if missing
380  $dk['l'] = strlen ( $body ); //optional
381  $dk['t'] = time ( ); // timestamp of signature, optional
382  if ( isset ( $this->valid_time ) )
383  $dk['x'] = $this->valid_time; // unix timestamp expiriation of signature, optional
384  $dk['h'] = implode ( ':', array_keys ( $headers ) );
385  $dk['bh'] = base64_encode ( hash ( 'sha256', $body , true ) );
386  $value = '';
387  foreach ( $dk as $key => $val )
388  $value .= "$key=$val; ";
389  $value .= 'b=';
390  $tosign = $b . 'DKIM-Signature: ' . $value;
391  openssl_sign ( $tosign, $sig, $this->schedule_private_key, $this->scheduling_dkim_algo );
392  $this->tosign = $tosign;
393  $value .= base64_encode ( $sig );
394  return $value;
395  }
396 
403  function sendRequest ( $address, $type, $data )
404  {
405  global $session;
406  if ( empty($this->scheduling_dkim_domain) )
407  return false;
408  if ( is_array ( $address ) )
409  list ( $user, $domain ) = explode ( '@', $address[0] );
410  else
411  list ( $user, $domain ) = explode ( '@', $address );
412  if ( ! $this->getCapabilities ( $domain ) )
413  {
414  dbg_error_log('ischedule', $domain . ' did not have iSchedule capabilities for ' . $type );
415  return false;
416  }
417  dbg_error_log('ischedule', $domain . ' trying with iSchedule capabilities for ' . $type );
418  if ( $this->queryCapabilities ( $type ) )
419  {
420  dbg_error_log('ischedule', $domain . ' trying with iSchedule capabilities for ' . $type . ' OK');
421  list ( $component, $method ) = explode ( '/', $type );
422  $headers = array ( );
423  $headers['iSchedule-Version'] = '1.0';
424  $headers['Originator'] = 'mailto:' . $session->email;
425  if ( is_array ( $address ) )
426  $headers['Recipient'] = implode ( ', ' , $address );
427  else
428  $headers['Recipient'] = $address;
429  $headers['Content-Type'] = 'text/calendar; component=' . $component ;
430  if ( $method )
431  $headers['Content-Type'] .= '; method=' . $method;
432  $headers['DKIM-Signature'] = $this->signDKIM ( $headers, $body );
433  if ( $headers['DKIM-Signature'] == false )
434  return false;
435  $request_headers = array ( );
436  foreach ( $headers as $k => $v )
437  $request_headers[] = $k . ': ' . $v;
438  $curl = curl_init ( $this->remote_url );
439  curl_setopt ( $curl, CURLOPT_RETURNTRANSFER, true );
440  curl_setopt ( $curl, CURLOPT_HTTPHEADER, array() ); // start with no headers set
441  curl_setopt ( $curl, CURLOPT_HTTPHEADER, $request_headers );
442  curl_setopt ( $curl, CURLOPT_SSL_VERIFYPEER, false);
443  curl_setopt ( $curl, CURLOPT_SSL_VERIFYHOST, false);
444  curl_setopt ( $curl, CURLOPT_POST, 1);
445  curl_setopt ( $curl, CURLOPT_POSTFIELDS, $data);
446  curl_setopt ( $curl, CURLOPT_CUSTOMREQUEST, 'POST' );
447  $xmlresponse = curl_exec ( $curl );
448  $info = curl_getinfo ( $curl );
449  curl_close ( $curl );
450  if ( $info['http_code'] >= 400 )
451  {
452  dbg_error_log ( 'ischedule', 'remote server returned error (%s)', $info['http_code'] );
453  return false;
454  }
455 
456  error_log ( 'remote response '. $xmlresponse . print_r ( $info, true ) );
457  $xml_parser = xml_parser_create_ns('UTF-8');
458  $xml_tags = array();
459  xml_parser_set_option ( $xml_parser, XML_OPTION_SKIP_WHITE, 1 );
460  xml_parser_set_option ( $xml_parser, XML_OPTION_CASE_FOLDING, 0 );
461  $rc = xml_parse_into_struct( $xml_parser, $xmlresponse, $xml_tags );
462  if ( $rc == false ) {
463  dbg_error_log( 'ERROR', 'XML parsing error: %s at line %d, column %d',
464  xml_error_string(xml_get_error_code($xml_parser)),
465  xml_get_current_line_number($xml_parser), xml_get_current_column_number($xml_parser) );
466  return false;
467  }
468  $xmltree = BuildXMLTree( $xml_tags );
469  xml_parser_free($xml_parser);
470  if ( !is_object($xmltree) ) {
471  dbg_error_log( 'ERROR', 'iSchedule RESPONSE body is not valid XML data!' );
472  return false;
473  }
474  $resp = $xmltree->GetPath ( '/*/urn:ietf:params:xml:ns:ischedule:response' );
475  $result = array();
476  foreach ( $resp as $r )
477  {
478  $recipient = $r->GetElements ( 'urn:ietf:params:xml:ns:ischedule:recipient' );
479  $status = $r->GetElements ( 'urn:ietf:params:xml:ns:ischedule:request-status' );
480  $calendardata = $r->GetElements ( 'urn:ietf:params:xml:ns:ischedule:calendar-data' );
481  if ( count ( $recipient ) < 1 )
482  continue; // this should be an error
483  if ( count ( $calendardata ) > 0 )
484  {
485  $result [ $recipient[0]->GetContent() ] = $calendardata[0]->GetContent();
486  }
487  else
488  {
489  $result [ $recipient[0]->GetContent() ] = $status[0]->GetContent();
490  }
491  }
492  if ( count ( $result ) < 1 )
493  return false;
494  else
495  return $result;
496  }
497  else
498  return false;
499  }
500 
506  function parseDKIM ( $sig )
507  {
508 
509  $this->failed = true;
510  $tags = preg_split ( '/;[\s\t]/', $sig );
511  foreach ( $tags as $v )
512  {
513  list($key,$value) = preg_split ( '/=/', $v, 2 );
514  $dkim[$key] = $value;
515  }
516  // the canonicalization method is currently undefined as of draft-01 of the iSchedule spec
517  // but it does define the value, it should be simple-http. RFC4871 also defines two methods
518  // simple and relaxed, simple is probably the same as simple http
519  // relaxed allows for header case folding and whitespace folding, see section 3.4.4 of RFC4871
520  if ( ! preg_match ( '{(simple|simple-http|relaxed)(/(simple|simple-http|relaxed))?}', $dkim['c'], $matches ) ) // canonicalization method
521  return 'bad canonicalization:' . $dkim['c'] ;
522  if ( count ( $matches ) > 2 )
523  $this->body_cannon = $matches[2];
524  else
525  $this->body_cannon = $matches[1];
526  $this->header_cannon = $matches[1];
527  // signing algorythm REQUIRED
528  if ( $dkim['a'] != 'rsa-sha1' && $dkim['a'] != 'rsa-sha256' ) // we only support the minimum required
529  return 'bad signing algorythm:' . $dkim['a'] ;
530  // query method to retrieve public key, could/should we add https to the spec? REQUIRED
531  if ( $dkim['q'] != 'dns/txt' )
532  return 'bad query method';
533  // domain of the signing entity REQUIRED
534  if ( ! isset ( $dkim['d'] ) )
535  return 'missing signing domain';
536  $this->remote_server = $dkim['d'];
537  // identity of signing AGENT, OPTIONAL
538  if ( isset ( $dkim['i'] ) )
539  {
540  // if present, domain of the signing agent must be a match or a subdomain of the signing domain
541  if ( ! stristr ( $dkim['i'], $dkim['d'] ) ) // RFC4871 does not specify a case match requirement
542  return 'signing domain mismatch';
543  // grab the local part of the signing agent if it's an email address
544  if ( strstr ( $dkim [ 'i' ], '@' ) )
545  $this->remote_user = substr ( $dkim [ 'i' ], 0, strpos ( $dkim [ 'i' ], '@' ) - 1 );
546  }
547  // selector used to retrieve public key REQUIRED
548  if ( ! isset ( $dkim['s'] ) )
549  return 'missing selector';
550  $this->remote_selector = $dkim['s'];
551  // signed header fields, colon seperated REQUIRED
552  if ( ! isset ( $dkim['h'] ) )
553  return 'missing list of signed headers';
554  $this->signed_headers = preg_split ( '/:/', $dkim['h'] );
555 
556  $sh = Array ();
557  foreach ( $this->signed_headers as $h )
558  {
559  $sh[] = strtolower ( $h );
560  if ( in_array ( strtolower ( $h ), $this->disallowed_headers ) )
561  return "$h is NOT allowed in signed header fields per RFC4871 or iSchedule";
562  }
563  foreach ( $this->required_headers as $h )
564  if ( ! in_array ( strtolower ( $h ), $sh ) )
565  return "$h is REQUIRED but missing in signed header fields per iSchedule";
566  // body hash REQUIRED
567  if ( ! isset ( $dkim['bh'] ) )
568  return 'missing body signature';
569  // signed header hash REQUIRED
570  if ( ! isset ( $dkim['b'] ) )
571  return 'missing signature in b field';
572  // length of body used for signing
573  if ( isset ( $dkim['l'] ) )
574  $this->signed_length = $dkim['l'];
575  $this->failed = false;
576  $this->DKSig = $dkim;
577  return true;
578  }
579 
584  function parseURI ( $uri )
585  {
586  if ( preg_match ( '/^mailto:([^@]+)@([^\s\t\n]+)/', $uri, $matches ) )
587  {
588  $this->remote_user = $matches[1];
589  $this->domain = $matches[2];
590  }
591  else
592  return false;
593  }
594 
599  function verifySignature ( )
600  {
601  global $request,$c;
602  $this->failed = true;
603  $signed = '';
604  foreach ( $this->signed_headers as $h )
605  if ( isset ( $_SERVER['HTTP_' . strtoupper ( strtr ( $h, '-', '_' ) ) ] ) )
606  $signed .= "$h: " . $_SERVER['HTTP_' . strtoupper ( strtr ( $h, '-', '_' ) ) ] . "\r\n";
607  else
608  $signed .= "$h: " . $_SERVER[ strtoupper ( strtr ( $h, '-', '_' ) ) ] . "\r\n";
609  if ( ! isset ( $_SERVER['HTTP_ORIGINATOR'] ) || stripos ( $signed, 'Originator' ) === false ) //required header, must be signed
610  return "missing Originator";
611  if ( ! isset ( $_SERVER['HTTP_RECIPIENT'] ) || stripos ( $signed, 'Recipient' ) === false ) //required header, must be signed
612  return "missing Recipient";
613  if ( ! isset ( $_SERVER['HTTP_ISCHEDULE_VERSION'] ) || $_SERVER['HTTP_ISCHEDULE_VERSION'] != '1' ) //required header and we only speak version 1 for now
614  return "missing or mismatch ischedule-version header";
615  $body = $request->raw_post;
616  if ( ! isset ( $this->signed_length ) ) // Should we use the Content-Length header if the signed length is missing?
617  $this->signed_length = strlen ( $body );
618  else
619  $body = substr ( $body, 0, $this->signed_length );
620  if ( isset ( $this->remote_user_rule ) )
621  if ( $this->remote_user_rule != '*' && ! stristr ( $this->remote_user, $this->remote_user_rule ) )
622  return "remote user rule failure";
623  $hash_algo = preg_replace ( '/^.*(sha1|sha256).*/','$1', $this->DKSig['a'] );
624  $body_hash = base64_encode ( hash ( $hash_algo, $body , true ) );
625  if ( $this->DKSig['bh'] != $body_hash )
626  return "body hash mismatch";
627  $sig = $_SERVER['HTTP_DKIM_SIGNATURE'];
628  $sig = preg_replace ( '/ b=[^;\s\r\n\t]+/', ' b=', $sig );
629  $signed .= 'DKIM-Signature: ' . $sig;
630  $verify = openssl_verify ( $signed, base64_decode ( $this->DKSig['b'] ), $this->remote_public_key, $hash_algo );
631  if ( $verify != 1 )
632  {
633  openssl_sign ( $signed, $sigb, $this->schedule_private_key, $hash_algo );
634  $sigc = base64_encode ( $sigb );
635  $verify1 = openssl_verify ( $signed, $sigc, $this->remote_public_key, $hash_algo );
636  return "signature verification failed " . $this->remote_public_key . " \n\n". $sig . " \n" . $hash_algo . "\n". print_r ($verify,1) . " XX " . $verify1 . "\n";
637  }
638  $this->failed = false;
639  return true;
640  }
641 
645  function validateRequest ( )
646  {
647  global $request;
648  if ( isset ( $_SERVER['HTTP_DKIM_SIGNATURE'] ) )
649  $sig = $_SERVER['HTTP_DKIM_SIGNATURE'];
650  else
651  {
652  $request->DoResponse( 403, translate('DKIM signature missing') );
653  return false;
654  }
655  if ( isset ( $_SERVER['HTTP_ORGANIZER'] ) )
656  $request->DoResponse( 403, translate('Organizer Missing') );
657 
658  dbg_error_log ('ischedule','beginning validation');
659  $err = $this->parseDKIM ( $sig );
660  if ( $err !== true || $this->failed )
661  $request->DoResponse( 412, 'DKIM signature invalid ' . "\n" . $err . "\n" );
662  if ( ! $this->getTxt () || $this->failed ) // this could also be a 424 failed dependency response
663  $request->DoResponse( 400, translate('DKIM signature validation failed(DNS ERROR)') );
664  if ( ! $this->parseTxt () || $this->failed )
665  $request->DoResponse( 400, translate('DKIM signature validation failed(KEY Parse ERROR)') );
666  if ( ! $this->validateKey () || $this->failed )
667  $request->DoResponse( 400, translate('DKIM signature validation failed(KEY Validation ERROR)') );
668  $err = $this->verifySignature ();
669  if ( $err !== true || $this->failed )
670  $request->DoResponse( 412, translate('DKIM signature validation failed(Signature verification ERROR)') . '\n' . $err );
671  dbg_error_log ('ischedule','signature ok');
672  return true;
673  }
674 }
675 
iSchedule\parseURI
parseURI( $uri)
Definition: iSchedule.php:584
iSchedule\parseDKIM
parseDKIM( $sig)
Definition: iSchedule.php:506
iSchedule\validateRequest
validateRequest()
Definition: iSchedule.php:645
iSchedule\parseTxt
parseTxt()
Definition: iSchedule.php:122
iSchedule\getTxt
getTxt()
Definition: iSchedule.php:78
iSchedule\getCapabilities
getCapabilities( $domain=null)
Definition: iSchedule.php:256
iSchedule\signDKIM
signDKIM( $headers, $body)
Definition: iSchedule.php:361
iSchedule\getServer
getServer()
Definition: iSchedule.php:193
iSchedule\verifySignature
verifySignature()
Definition: iSchedule.php:599
iSchedule\queryCapabilities
queryCapabilities( $capability, $domain=null)
Definition: iSchedule.php:294
iSchedule
Definition: iSchedule.php:25
iSchedule\validateKey
validateKey()
Definition: iSchedule.php:144
iSchedule\sendRequest
sendRequest( $address, $type, $data)
Definition: iSchedule.php:403
iSchedule\setTxt
setTxt( $dk)
Definition: iSchedule.php:114