From 33510a2f4deb53e64ad32ee6231aa9d90ca3c55f Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Tue, 2 Oct 2012 22:30:36 +0000 Subject: [PATCH] dkim signature for posts using mailDomainSigner class, plus some code to generate and store a key-pair --- phpgwapi/inc/class.ischedule_client.inc.php | 64 +++++++++++++++++++-- phpgwapi/ischedule-cli.php | 35 +++++++++-- 2 files changed, 89 insertions(+), 10 deletions(-) diff --git a/phpgwapi/inc/class.ischedule_client.inc.php b/phpgwapi/inc/class.ischedule_client.inc.php index aabbd94d1d..987011eff4 100644 --- a/phpgwapi/inc/class.ischedule_client.inc.php +++ b/phpgwapi/inc/class.ischedule_client.inc.php @@ -55,6 +55,34 @@ class ischedule_client { $this->url = $url; } + + $this->dkim_private_key = $GLOBALS['egw_info']['server']['dkim_private_key']; + } + + /** + * Generate private/public key pair + * + * Private and public key are stored in api config as dkim_private_key / dkim_public_key and loaded automatic by constructor. + * + * @return string public key + */ + public static function generateKeyPair() + { + // Create the keypair + $res = openssl_pkey_new(); + + // Get private key + openssl_pkey_export($res, $dkim_private_key); + + // Get public key + $details = openssl_pkey_get_details($res); + $dkim_public_key = $details['key']; + + // store both in config + config::save_value('dkim_private_key', $dkim_private_key, 'phpgwapi'); + config::save_value('dkim_public_key', $dkim_public_key, 'phpgwapi'); + + return $dkim_public_key; } const EMAIL_PREG = '/^([a-z0-9][a-z0-9._-]*)?[a-z0-9]@([a-z0-9](|[a-z0-9_-]*[a-z0-9])\.)+[a-z]{2,6}$/i'; @@ -131,11 +159,16 @@ class ischedule_client * * @param string $content * @param string $content_type + * @param boolean $debug=false true echo request before posting * @return string * @throws Exception with http status code and message, if server responds other then 2xx */ - public function post_msg($content, $content_type) + public function post_msg($content, $content_type, $debug=false) { + if (empty($this->dkim_private_key)) + { + throw new Exception('You need to generate a key pair first!'); + } $url_parts = parse_url($this->url); $headers = array( 'Host' => $url_parts['host'].($url_parts['port'] ? ':'.$url_parts['port'] : ''), @@ -145,12 +178,13 @@ class ischedule_client 'Recipient' => $this->recipient, 'Content-Length' => bytes($content), ); - $headers['DKIM-Signature'] = $this->dkim_sign($headers, $content); $header_string = ''; foreach($headers as $name => $value) { $header_string .= $name.': '.$value."\r\n"; } + $header_string .= $this->dkim_sign($headers, $content)."\r\n"; + $opts = array('http' => array( 'method' => 'POST', @@ -160,6 +194,8 @@ class ischedule_client ) ); + if ($debug) echo "POST $this->url HTTP/1.1\n$header_string\n$content\n"; + // need to suppress warning, if http-status not 2xx if (($response = @file_get_contents($this->url, false, stream_context_create($opts))) === false) { @@ -172,13 +208,29 @@ class ischedule_client /** * Calculate DKIM signature for headers and body using originators domains private key * - * @param array $headers + * @param array $headers name => value pairs, names as in $sign_headers * @param string $body - * @param string $type dkim-type + * @param string $selector='calendar' + * @param string $sign_headers='Content-Type:Host:Originator:Recipient' + * @return string DKIM-Signature: ... */ - public function dkim_sign(array $headers, $body, $type='calendar') + public function dkim_sign(array $headers, $body, $selector='calendar', $sign_headers='Content-Type:Host:Originator:Recipient') { - return 'dummy'; + include_once EGW_API_INC.'/php-mail-domain-signer/lib/class.mailDomainSigner.php'; + list(,$domain) = explode('@', $this->originator); + $mds = new mailDomainSigner($this->dkim_private_key, $domain, $selector); + + $dkim_headers = array(); + foreach(explode(':', $sign_headers) as $header) + { + $dkim_headers[] = $header.': '.$headers[$header]; + } + $dkim = $mds->getDKIM(strtolower($sign_headers), $dkim_headers, $body); + + // as we do http, no need to fold dkim, in fact recommendation is not to + $dkim = str_replace(array(";\r\n\t", "\r\n\t"), array('; ', ''), $dkim); + + return $dkim; } /** diff --git a/phpgwapi/ischedule-cli.php b/phpgwapi/ischedule-cli.php index 943320325c..5de39a9802 100755 --- a/phpgwapi/ischedule-cli.php +++ b/phpgwapi/ischedule-cli.php @@ -21,7 +21,15 @@ if (isset($_SERVER['HTTP_HOST'])) die("This is a commandline ONLY tool!\n"); */ function usage($err=null) { - echo basename(__FILE__).": [--url ischedule-url] [--component (VEVENT|VFREEBUSY|VTODO) (-|ical-filename)] [--method (REQUEST(default)|RESPONSE)] recipient-email [originator-email]\n\n"; + echo "\nUsage: ".basename(__FILE__).": [options] recipient-email [originator-email]\n\n"; + echo "available options:\n\n"; + echo "\t--url ischedule-url\n"; + echo "\t--component (VEVENT|VFREEBUSY|VTODO) (-|ical-filename)\n"; + echo "\t--method (REQUEST(default)|REPLY|CANCEL|ADD)\n"; + echo "\t--generate-key-pair : generates and stores a new key pair\n"; + echo "\t--public-key : outputs public key\n"; + echo "\t-v|--verbose output posted message too\n"; + echo "\n"; if ($err) echo "$err\n\n"; exit; } @@ -42,10 +50,10 @@ $method = 'REQUEST'; while($args[0][0] == '-') { $option = array_shift($args); - if (count($args) < 2) usage("Missing arguments for '$option'!".array2string($args)); switch($option) { case '--url': + if (count($args) < 2) usage("Missing arguments for '$option'!"); $url = array_shift($args); break; @@ -64,6 +72,7 @@ while($args[0][0] == '-') break; case '--method': + if (count($args) < 2) usage("Missing arguments for '$option'!"); $method = strtoupper(array_shift($args)); if (!in_array($method, array('REQUEST','REPLY','CANCEL','ADD'))) { @@ -71,23 +80,41 @@ while($args[0][0] == '-') } break; + case '-v': + case '--verbose': + $verbose = true; + break; + + case '--generate-key-pair': + $GLOBALS['egw_info']['server']['dkim_public_key'] = ischedule_client::generateKeyPair(); + echo "\nKey pair generated\n"; + // fall through + case '--public-key': + if (empty($GLOBALS['egw_info']['server']['dkim_public_key'])) die("\nYou need to generate a key pair first!\n\n"); + echo "\nYou need following DNS record:\n"; + $public_key = preg_replace('/([-]+(BEGIN|END) PUBLIC KEY[-]+|\s*)/m', '', $GLOBALS['egw_info']['server']['dkim_public_key']); + echo "\ncalendar._domainkey IN TXT \"v=DKIM1;k=rsa;h=sha1;s=calendar;t=s;p=$public_key\"\n\n"; + exit; + default: usage("Unknown option '$option'!"); } } -if (!count($args)) usage(); +if (!count($args) && !($public_key || $generate_key_pair)) usage(); $recipient = array_shift($args); if ($args) $originator = array_shift($args); try { $client = new ischedule_client($recipient, $url); + echo "\nUsing iSchedule URL: $client->url\n\n"; if ($originator) $client->setOriginator($originator); if ($component) { $content_type = 'text/calendar; component='.$component.'; method='.$method; - echo $client->post_msg($content, $content_type); + $response = $client->post_msg($content, $content_type, $verbose); + echo $response; } else {