mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-11-26 09:53:20 +01:00
added dkim signature validation to ischedule_server
This commit is contained in:
parent
33510a2f4d
commit
0d5ae0e2c2
@ -75,6 +75,7 @@ class ischedule_server
|
|||||||
*/
|
*/
|
||||||
protected function post()
|
protected function post()
|
||||||
{
|
{
|
||||||
|
// get and verify required headers
|
||||||
static $required_headers = array('Host','Recipient','Originator','Content-Type','DKIM-Signature');
|
static $required_headers = array('Host','Recipient','Originator','Content-Type','DKIM-Signature');
|
||||||
$headers = array();
|
$headers = array();
|
||||||
foreach($required_headers as $header)
|
foreach($required_headers as $header)
|
||||||
@ -83,13 +84,18 @@ class ischedule_server
|
|||||||
if (strpos($server_name, 'CONTENT_') !== 0) $server_name = 'HTTP_'.$server_name;
|
if (strpos($server_name, 'CONTENT_') !== 0) $server_name = 'HTTP_'.$server_name;
|
||||||
if (!empty($_SERVER[$server_name])) $headers[$header] = $_SERVER[$server_name];
|
if (!empty($_SERVER[$server_name])) $headers[$header] = $_SERVER[$server_name];
|
||||||
}
|
}
|
||||||
if (($missing = array_diff($required_headers, array_keys($headers))))
|
if (($missing = array_diff(array_keys($headers), $required_headers)))
|
||||||
{
|
{
|
||||||
throw new Exception ('Bad Request: missing '.implode(', ', $missing).' header(s)', 400);
|
throw new Exception ('Bad Request: missing '.implode(', ', $missing).' header(s)', 400);
|
||||||
}
|
}
|
||||||
if (!$this->dkim_validate($headers))
|
|
||||||
|
// get raw request body
|
||||||
|
$ical = file_get_contents('php://input');
|
||||||
|
|
||||||
|
// validate dkim signature
|
||||||
|
if (!self::dkim_validate($headers, $ical, $error))
|
||||||
{
|
{
|
||||||
throw new Exception('Bad Request: DKIM signature invalid', 400);
|
throw new Exception('Bad Request: DKIM signature invalid: '.$error, 400);
|
||||||
}
|
}
|
||||||
// check if recipient is a user
|
// check if recipient is a user
|
||||||
// ToDo: multiple recipients
|
// ToDo: multiple recipients
|
||||||
@ -127,7 +133,6 @@ class ischedule_server
|
|||||||
throw new Exception ('Bad Request: missing or unsupported method in Content-Type header', 400);
|
throw new Exception ('Bad Request: missing or unsupported method in Content-Type header', 400);
|
||||||
}
|
}
|
||||||
// parse iCal
|
// parse iCal
|
||||||
$ical = file_get_contents('php://input');
|
|
||||||
// code copied from calendar_groupdav::outbox_freebusy_request for now
|
// code copied from calendar_groupdav::outbox_freebusy_request for now
|
||||||
include_once EGW_SERVER_ROOT.'/phpgwapi/inc/horde/lib/core.php';
|
include_once EGW_SERVER_ROOT.'/phpgwapi/inc/horde/lib/core.php';
|
||||||
$vcal = new Horde_iCalendar();
|
$vcal = new Horde_iCalendar();
|
||||||
@ -183,6 +188,10 @@ class ischedule_server
|
|||||||
$this->vfreebusy($event, $handler, $vcal_comp, $xml);
|
$this->vfreebusy($event, $handler, $vcal_comp, $xml);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'VEVENT':
|
||||||
|
$this->vevent($event, $handler, $component, $xml);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new exception('Not yet implemented!');
|
throw new exception('Not yet implemented!');
|
||||||
}
|
}
|
||||||
@ -196,6 +205,42 @@ class ischedule_server
|
|||||||
echo $xml->outputMemory();
|
echo $xml->outputMemory();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle VEVENT component
|
||||||
|
*
|
||||||
|
* @param array $event
|
||||||
|
* @param calendar_ical $handler
|
||||||
|
* @param string $ical
|
||||||
|
* @param XMLWriter $xml
|
||||||
|
*/
|
||||||
|
function vevent(array $event, calendar_ical $handler, Horde_iCalendar_vevent $component, XMLWriter $xml)
|
||||||
|
{
|
||||||
|
$organizer = $component->getAttribute('ORGANIZER');
|
||||||
|
$attendees = (array)$component->getAttribute('ATTENDEE');
|
||||||
|
|
||||||
|
$handler->importVCal($vCalendar, $eventId,
|
||||||
|
self::etag2value($this->http_if_match), false, 0, $this->groupdav->current_user_principal, $user, $charset, $id);
|
||||||
|
|
||||||
|
foreach($event['participants'] as $uid => $status)
|
||||||
|
{
|
||||||
|
$xml->startElement('response');
|
||||||
|
|
||||||
|
$xml->writeElement('recipient', $attendee=array_shift($attendees)); // iSchedule has not DAV:href!
|
||||||
|
|
||||||
|
if (is_numeric($uid))
|
||||||
|
{
|
||||||
|
$xml->writeElement('request-status', '2.0;Success');
|
||||||
|
$xml->writeElement('responsedescription', 'Delivered to recipient');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$xml->writeElement('request-status', '3.7;Invalid Calendar User');
|
||||||
|
$xml->writeElement('responsedescription', 'Recipient not a local user');
|
||||||
|
}
|
||||||
|
$xml->endElement(); // response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle VFREEBUSY component
|
* Handle VFREEBUSY component
|
||||||
*
|
*
|
||||||
@ -233,16 +278,102 @@ class ischedule_server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DKIM_HEADERS = 'content-type:host:originator:recipient';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate DKIM signature
|
* Validate DKIM signature
|
||||||
*
|
*
|
||||||
* @param array $headers
|
* @param array $headers
|
||||||
|
* @param string $body
|
||||||
|
* @param string &$error=null error if false returned
|
||||||
* @return boolean true if signature could be validated, false otherwise
|
* @return boolean true if signature could be validated, false otherwise
|
||||||
* @todo
|
* @todo
|
||||||
*/
|
*/
|
||||||
public function dkim_validate(array $headers)
|
public static function dkim_validate(array $headers, $body, &$error=null, $verify_headers='Content-Type:Host:Originator:Recipient')
|
||||||
{
|
{
|
||||||
return isset($headers['DKIM-Signature']);
|
// parse dkim siginature
|
||||||
|
if (!isset($headers['DKIM-Signature']) ||
|
||||||
|
!preg_match_all('/[\t\s]*([a-z]+)=([^;]+);?/i', $headers['DKIM-Signature'], $matches))
|
||||||
|
{
|
||||||
|
$error = "Can't parse DKIM signature";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$dkim = array_combine($matches[1], $matches[2]);
|
||||||
|
|
||||||
|
if (array_diff(explode(':', $dkim['h']), explode(':', strtolower($verify_headers))))
|
||||||
|
{
|
||||||
|
$error = "Missing required headers h=$dkim[h]";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch public key
|
||||||
|
if (!($dns = self::fetch_dns($dkim['d'], $dkim['s'])))
|
||||||
|
{
|
||||||
|
$error = "No public key for d='$dkim[d]' and s='$dkim[s]'";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$public_key = "-----BEGIN PUBLIC KEY-----\n".chunk_split($dns['p'], 64, "\n")."-----END PUBLIC KEY-----\n";
|
||||||
|
|
||||||
|
// create headers array
|
||||||
|
$dkim_headers = array();
|
||||||
|
foreach(explode(':', $verify_headers) as $header)
|
||||||
|
{
|
||||||
|
$dkim_headers[] = $header.': '.$headers[$header];
|
||||||
|
}
|
||||||
|
list($dkim_unsigned) = explode('b=', 'DKIM-Signature: '.$headers['DKIM-Signature']);
|
||||||
|
$dkim_unsigned .= 'b=';
|
||||||
|
|
||||||
|
// Canonicalization Header Data
|
||||||
|
require_once EGW_API_INC.'/php-mail-domain-signer/lib/class.mailDomainSigner.php';
|
||||||
|
$_unsigned = mailDomainSigner::headRelaxCanon(implode("\r\n", $dkim_headers). "\r\n".$dkim_unsigned);
|
||||||
|
|
||||||
|
$ok = openssl_verify($_unsigned, base64_decode($dkim['b']), $public_key);
|
||||||
|
|
||||||
|
switch($ok)
|
||||||
|
{
|
||||||
|
case -1:
|
||||||
|
$error = 'Error while verifying DKIM';
|
||||||
|
return false;
|
||||||
|
|
||||||
|
case 0:
|
||||||
|
$error = 'DKIM signature does NOT verify';
|
||||||
|
error_log(__METHOD__."() unsigned='$_unsigned' $error");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Relax Canonicalization for Body
|
||||||
|
$_b = mailDomainSigner::bodyRelaxCanon($body);
|
||||||
|
// Hash of the canonicalized body [tag:bh]
|
||||||
|
$_bh= base64_encode(sha1($_b,true));
|
||||||
|
|
||||||
|
// check body hash
|
||||||
|
if ($_bh != $dkim['bh'])
|
||||||
|
{
|
||||||
|
$error = 'Body hash does NOT verify';
|
||||||
|
error_log(__METHOD__."() body-hash='$_bh' != '$dkim[bh]'=dkim-bh $error");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch dns record and return parsed array
|
||||||
|
*
|
||||||
|
* @param string $domain
|
||||||
|
* @param string $selector
|
||||||
|
* @return array with values for keys parsed from eg. "v=DKIM1\;k=rsa\;h=sha1\;s=calendar\;t=s\;p=..."
|
||||||
|
*/
|
||||||
|
public static function fetch_dns($domain, $selector='calendar')
|
||||||
|
{
|
||||||
|
if (!($records = dns_get_record($host=$selector.'._domainkey.'.$domain, DNS_TXT))) return false;
|
||||||
|
|
||||||
|
if (!isset($records[0]['text']) &&
|
||||||
|
!preg_match_all('/[\t\s]*([a-z]+)=([^;]+);?/i', $records[0]['txt'], $matches))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return array_combine($matches[1], $matches[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user