From bd6efa0e4529b6e0c1049d725aeb6c530b3cff21 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Mon, 11 Jan 2016 19:55:02 +0100 Subject: [PATCH] Update PuSH callback URL if remote side switched to HTTPS See the comment in the source on why we're not following Location headers... --- plugins/OStatus/classes/HubSub.php | 80 +++++++++++++++++++++++------- 1 file changed, 62 insertions(+), 18 deletions(-) diff --git a/plugins/OStatus/classes/HubSub.php b/plugins/OStatus/classes/HubSub.php index c9d65c56a7..68151b19a1 100644 --- a/plugins/OStatus/classes/HubSub.php +++ b/plugins/OStatus/classes/HubSub.php @@ -40,7 +40,7 @@ class HubSub extends Managed_DataObject public $created; public $modified; - protected static function hashkey($topic, $callback) + static function hashkey($topic, $callback) { return sha1($topic . '|' . $callback); } @@ -120,6 +120,11 @@ class HubSub extends Managed_DataObject $qm->enqueue($data, 'hubconf'); } + public function getTopic() + { + return $this->topic; + } + /** * Send a verification ping to subscriber, and if confirmed apply the changes. * This may create, update, or delete the database record. @@ -134,7 +139,7 @@ class HubSub extends Managed_DataObject $challenge = common_random_hexstr(32); $params = array('hub.mode' => $mode, - 'hub.topic' => $this->topic, + 'hub.topic' => $this->getTopic(), 'hub.challenge' => $challenge); if ($mode == 'subscribe') { $params['hub.lease_seconds'] = $this->lease; @@ -157,13 +162,13 @@ class HubSub extends Managed_DataObject $status = $response->getStatus(); if ($status >= 200 && $status < 300) { - common_log(LOG_INFO, "Verified {$mode} of {$this->callback}:{$this->topic}"); + common_log(LOG_INFO, "Verified {$mode} of {$this->callback}:{$this->getTopic()}"); } else { // TRANS: Client exception. %s is a HTTP status code. throw new ClientException(sprintf(_m('Hub subscriber verification returned HTTP %s.'),$status)); } - $old = HubSub::getByHashkey($this->topic, $this->callback); + $old = HubSub::getByHashkey($this->getTopic(), $this->callback); if ($mode == 'subscribe') { if ($old instanceof HubSub) { $this->update($old); @@ -185,7 +190,7 @@ class HubSub extends Managed_DataObject */ function insert() { - $this->hashkey = self::hashkey($this->topic, $this->callback); + $this->hashkey = self::hashkey($this->getTopic(), $this->callback); $this->created = common_sql_now(); $this->modified = common_sql_now(); return parent::insert(); @@ -208,11 +213,11 @@ class HubSub extends Managed_DataObject // destroy the result data for the parent query. // @fixme use clone() again when it's safe to copy an // individual item from a multi-item query again. - $sub = HubSub::getByHashkey($this->topic, $this->callback); + $sub = HubSub::getByHashkey($this->getTopic(), $this->callback); $data = array('sub' => $sub, 'atom' => $atom, 'retries' => $retries); - common_log(LOG_INFO, "Queuing PuSH: $this->topic to $this->callback"); + common_log(LOG_INFO, "Queuing PuSH: {$this->getTopic()} to {$this->callback}"); $qm = QueueManager::get(); $qm->enqueue($data, 'hubout'); } @@ -229,10 +234,9 @@ class HubSub extends Managed_DataObject function bulkDistribute($atom, $pushCallbacks) { $data = array('atom' => $atom, - 'topic' => $this->topic, + 'topic' => $this->getTopic(), 'pushCallbacks' => $pushCallbacks); - common_log(LOG_INFO, "Queuing PuSH batch: $this->topic to " . - count($pushCallbacks) . " sites"); + common_log(LOG_INFO, "Queuing PuSH batch: {$this->getTopic()} to ".count($pushCallbacks)." sites"); $qm = QueueManager::get(); $qm->enqueue($data, 'hubprep'); } @@ -256,18 +260,58 @@ class HubSub extends Managed_DataObject } else { $hmac = '(none)'; } - common_log(LOG_INFO, "About to push feed to $this->callback for $this->topic, HMAC $hmac"); + common_log(LOG_INFO, "About to push feed to $this->callback for {$this->getTopic()}, HMAC $hmac"); $request = new HTTPClient(); $request->setBody($atom); - $response = $request->post($this->callback, $headers); + try { + $response = $request->post($this->callback, $headers); - if ($response->isOk()) { - return true; - } else { - // TRANS: Exception. %1$s is a response status code, %2$s is the body of the response. - throw new Exception(sprintf(_m('Callback returned status: %1$s. Body: %2$s'), - $response->getStatus(),trim($response->getBody()))); + if ($response->isOk()) { + return true; + } + } catch (Exception $e) { + $response = null; + + common_debug('PuSH callback to '._ve($this->callback).' for '._ve($this->getTopic()).' failed with exception: '._ve($e->getMessage())); } + + // XXX: DO NOT trust a Location header here, _especially_ from 'http' protocols, + // but not 'https' either at least if we don't do proper CA verification. Trust that + // the most common change here is simply switching 'http' to 'https' and we will + // solve 99% of all of these issues for now. There should be a proper mechanism + // if we want to change the callback URLs, preferrably just manual resubscriptions + // from the remote side, combined with implemented PuSH subscription timeouts. + + // We failed the PuSH, but it might be that the remote site has changed their configuration to HTTPS + if ('http' === parse_url($this->callback, PHP_URL_SCHEME)) { + // Test if the feed callback for this node has migrated to HTTPS + $httpscallback = preg_replace('/^http/', 'https', $this->callback, 1); + if ($httpscallback === $this->callback) { + throw new ServerException('Trying to preg_replace http to https on '._ve($this->callback).' failed and resulted in an identical string: '._ve($httpscallback).'.'); + } + common_debug('PuSH callback to '._ve($this->callback).' for '._ve($this->getTopic()).' testing with HTTPS callback: '._ve($httpscallback)); + $response = $request->post($httpscallback, $headers); + if ($response->isOk()) { + $orig = clone($this); + $this->callback = $httpscallback; + $this->hashkey = self::hashkey($this->getTopic(), $this->callback); + common_debug('HubSub DEBUG, from '._ve($orig).' to '._ve($this)); + $this->updateWithKeys($orig, 'hashkey'); + return true; + } + } + + // FIXME: Add 'failed' incremental count for this callback. + + if (is_null($response)) { + // This means we got a lower-than-HTTP level error, like domain not found or maybe connection refused + // This should be using a more distinguishable exception class, but for now this will do. + throw new Exception(sprintf(_m('HTTP request failed without response to URL: %s'), var_export($target, true))); + } + + // TRANS: Exception. %1$s is a response status code, %2$s is the body of the response. + throw new Exception(sprintf(_m('Callback returned status: %1$s. Body: %2$s'), + $response->getStatus(),trim($response->getBody()))); } } -- 2.39.5