PageRenderTime 53ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/03_Development/profiles/acquia/modules/acquia_connector/acquia_agent/acquia_agent.module

https://bitbucket.org/nstjelja/dhk
Unknown | 709 lines | 657 code | 52 blank | 0 comment | 0 complexity | 588b6cb5dab9d983b115a6e6eb77322c MD5 | raw file
Possible License(s): GPL-2.0, AGPL-1.0
  1. <?php
  2. /**
  3. * @file
  4. * Acquia Agent securely sends information to Acquia Network.
  5. */
  6. /**
  7. * XML-RPC errors defined by the Acquia Network.
  8. */
  9. define('SUBSCRIPTION_NOT_FOUND' , 1000);
  10. define('SUBSCRIPTION_KEY_MISMATCH' , 1100);
  11. define('SUBSCRIPTION_EXPIRED' , 1200);
  12. define('SUBSCRIPTION_REPLAY_ATTACK' , 1300);
  13. define('SUBSCRIPTION_KEY_NOT_FOUND' , 1400);
  14. define('SUBSCRIPTION_MESSAGE_FUTURE' , 1500);
  15. define('SUBSCRIPTION_MESSAGE_EXPIRED' , 1600);
  16. define('SUBSCRIPTION_MESSAGE_INVALID' , 1700);
  17. define('SUBSCRIPTION_VALIDATION_ERROR', 1800);
  18. define('SUBSCRIPTION_PROVISION_ERROR' , 9000);
  19. /**
  20. * Subscription message lifetime defined by the Acquia Network.
  21. */
  22. define('SUBSCRIPTION_MESSAGE_LIFETIME', 15*60);
  23. /**
  24. * Implementation of hook_menu().
  25. */
  26. function acquia_agent_menu() {
  27. $items['admin/config/system/acquia-agent'] = array(
  28. 'title' => 'Acquia Network settings',
  29. 'description' => 'Connect your site to the Acquia Network.',
  30. 'page callback' => 'acquia_agent_settings_page',
  31. 'file' => 'acquia_agent.pages.inc',
  32. 'access arguments' => array('administer site configuration'),
  33. );
  34. $items['admin/config/system/acquia-agent/setup'] = array(
  35. 'title' => 'Acquia Network automatic setup',
  36. 'description' => 'Connect your site to the Acquia Network.',
  37. 'page callback' => 'drupal_get_form',
  38. 'page arguments' => array('acquia_agent_automatic_setup_form'),
  39. 'file' => 'acquia_agent.pages.inc',
  40. 'access arguments' => array('administer site configuration'),
  41. 'type' => MENU_CALLBACK,
  42. );
  43. $items['admin/config/system/acquia-agent/credentials'] = array(
  44. 'title' => 'Acquia Network credentials',
  45. 'description' => 'Connect your site to the Acquia Network.',
  46. 'page callback' => 'drupal_get_form',
  47. 'page arguments' => array('acquia_agent_settings_credentials'),
  48. 'file' => 'acquia_agent.pages.inc',
  49. 'access arguments' => array('administer site configuration'),
  50. 'type' => MENU_CALLBACK,
  51. );
  52. $items['admin/config/system/acquia-agent/migrate'] = array(
  53. 'title' => 'Acquia Cloud Migrate',
  54. 'description' => 'Migrate your site to Acquia Cloud.',
  55. 'page callback' => 'acquia_agent_migrate_page',
  56. 'file' => 'acquia_agent.pages.inc',
  57. 'access arguments' => array('administer site configuration'),
  58. 'type' => MENU_CALLBACK,
  59. );
  60. $items['system/acquia-migrate-check'] = array(
  61. 'title' => 'Upload capable',
  62. 'description' => 'Check for Acquia Cloud migrate capabilities',
  63. 'page callback' => 'acquia_agent_migrate_check',
  64. 'file' => 'acquia_agent.migrate.inc',
  65. 'access arguments' => array('access content'),
  66. 'type' => MENU_CALLBACK,
  67. );
  68. $items['admin/config/system/acquia-agent/refresh-status'] = array(
  69. 'title' => 'Manual update check',
  70. 'page callback' => 'acquia_agent_refresh_status',
  71. 'access arguments' => array('administer site configuration'),
  72. 'type' => MENU_CALLBACK,
  73. );
  74. return $items;
  75. }
  76. /**
  77. * Implement hook_page_alter().
  78. */
  79. function acquia_agent_page_alter(&$page) {
  80. if (isset($page['page_top']['toolbar']) && user_access('administer site configuration')) {
  81. $page['page_top']['toolbar']['#pre_render'][] = 'acquia_agent_toolbar_add_links';
  82. }
  83. }
  84. /**
  85. * Pre-render function which dynamically adds links to the toolbar.
  86. */
  87. function acquia_agent_toolbar_add_links($toolbar) {
  88. $link['html'] = TRUE;
  89. if (acquia_agent_subscription_is_active()) {
  90. $subscription = acquia_agent_settings('acquia_subscription_data');
  91. // Yes, this uses inline CSS, which sounds bad, but including a CSS file
  92. // just for this sounds equally bad.
  93. $icon = '<img src="'. base_path() . 'misc/message-16-ok.png" alt="ok" style="vertical-align: middle;" />';
  94. $link['title'] = t("!icon Subscription active (expires @date)", array('!icon' => $icon, '@date' => format_date(strtotime($subscription['expiration_date']['value']), 'custom', 'Y/n/j')));
  95. $link['attributes']['class'][] = "acquia-active-subscription";
  96. $link['attributes']['title'] = $subscription['product']['view'];
  97. $link['href'] = $subscription['href'];
  98. }
  99. else {
  100. // Yes, this uses inline CSS, which sounds bad, but including a CSS file
  101. // just for this sounds equally bad.
  102. $icon = '<img src="'. base_path() . 'misc/message-16-error.png" alt="error" style="vertical-align: middle;" />';
  103. $link['title'] = t("!icon Subscription not active", array('!icon' => $icon));
  104. $link['attributes']['class'][] = "acquia-inactive-subscription";
  105. $link['href'] = 'http://acquia.com/network';
  106. }
  107. $toolbar['toolbar_user']['#links'] = array_merge(array('acquia_agent' => $link), $toolbar['toolbar_user']['#links']);
  108. return $toolbar;
  109. }
  110. /**
  111. * Implementation of hook_init().
  112. */
  113. function acquia_agent_init() {
  114. if ((arg(0) != 'overlay-ajax') &&
  115. (arg(3) != 'acquia-agent') &&
  116. empty($_POST) &&
  117. user_access('administer site configuration') &&
  118. (!acquia_agent_has_credentials())) {
  119. $text = 'Get a <a href="!acquia-free">free 30-day trial</a> of the Acquia Network which includes a <a href="!library" target="_blank">library</a> of expert knowledge, <a href="!services" target="_blank">tools and services</a> to make your site awesome, and <a href="!support" target="_blank">Drupal support</a> when you need it.<br/>If you have an Acquia Network subscription, <a href="!settings">connect now</a>. Otherwise, you can turn this message off by disabling the Acquia Network <a href="!admin-modules">modules</a>.';
  120. $message = t($text, array('!acquia-free' => url('admin/config/system/acquia-agent'), '!library' => url('http://library.acquia.com'), '!settings' => url('admin/config/system/acquia-agent/setup'), '!support' => url('http://www.acquia.com/drupal-support'), '!services' => url('http://www.acquia.com/products-services/acquia-network/cloud-services'), '!admin-modules' => url('admin/modules')));
  121. drupal_set_message($message, 'warning', FALSE);
  122. }
  123. }
  124. /**
  125. * Implementation of hook_theme().
  126. */
  127. function acquia_agent_theme() {
  128. return array(
  129. 'acquia_agent_banner_form' => array(
  130. 'render element' => 'form',
  131. 'file' => 'acquia_agent.pages.inc',
  132. ),
  133. );
  134. }
  135. /**
  136. * Get subscription status from the Acquia Network, and store the result.
  137. *
  138. * This check also sends a heartbeat to the Acquia Network unless
  139. * $params['no_heartbeat'] == 1.
  140. */
  141. function acquia_agent_check_subscription($params = array()) {
  142. // Default return value is FALSE.
  143. $subscription = FALSE;
  144. if (!acquia_agent_has_credentials()) {
  145. // If there is not an identifier or key, delete any old subscription data.
  146. variable_del('acquia_subscription_data');
  147. }
  148. else {
  149. // There is an identifier and key, so attempt communication.
  150. // Include version number information.
  151. acquia_agent_load_versions();
  152. if (IS_ACQUIA_DRUPAL) {
  153. $params['version'] = ACQUIA_DRUPAL_VERSION;
  154. $params['series'] = ACQUIA_DRUPAL_SERIES;
  155. $params['branch'] = ACQUIA_DRUPAL_BRANCH;
  156. $params['revision'] = ACQUIA_DRUPAL_REVISION;
  157. }
  158. // Include Acquia Search module version number.
  159. if (module_exists('acquia_search')) {
  160. foreach (array('acquia_search', 'apachesolr') as $name) {
  161. $info = system_get_info('module', $name);
  162. // Send the version, or at least the core compatibility as a fallback.
  163. $params['search_version'][$name] = isset($info['version']) ? (string)$info['version'] : (string)$info['core'];
  164. }
  165. }
  166. $data = acquia_agent_call('acquia.agent.subscription', $params);
  167. $subscription['timestamp'] = REQUEST_TIME;
  168. if ($errno = xmlrpc_errno()) {
  169. switch ($errno) {
  170. case SUBSCRIPTION_NOT_FOUND:
  171. case SUBSCRIPTION_EXPIRED:
  172. variable_del('acquia_subscription_data');
  173. break;
  174. }
  175. }
  176. elseif (acquia_agent_valid_response($data)) {
  177. $subscription += $data['result']['body'];
  178. variable_set('acquia_subscription_data', $subscription);
  179. }
  180. else {
  181. watchdog('acquia agent', 'HMAC validation error: <pre>@data</pre>', array('@data' => print_r($data, TRUE)), WATCHDOG_ERROR);
  182. }
  183. }
  184. module_invoke_all('acquia_subscription_status', acquia_agent_subscription_is_active());
  185. return $subscription;
  186. }
  187. function acquia_agent_report_xmlrpc_error() {
  188. drupal_set_message(t('Error: @message (@errno)', array('@message' => xmlrpc_error_msg(), '@errno' => xmlrpc_errno())), 'error');
  189. }
  190. /**
  191. * Implementation of hook_update_status_alter().
  192. *
  193. * This compares the array of computed information about projects that are
  194. * missing available updates with the saved settings. If the settings specify
  195. * that a particular project or release should be ignored, the status for that
  196. * project is altered to indicate it is ignored because of settings.
  197. *
  198. * @param $projects
  199. * Reference to an array of information about available updates to each
  200. * project installed on the system.
  201. *
  202. * @see update_calculate_project_data()
  203. */
  204. function acquia_agent_update_status_alter(&$projects) {
  205. if (!$subscription = acquia_agent_has_update_service()) {
  206. // Get subscription data or return if the service is not enabled.
  207. return;
  208. }
  209. foreach ($projects as $project => $project_info) {
  210. if ($project == 'drupal') {
  211. if (isset($subscription['update'])) {
  212. $projects[$project]['status'] = $subscription['update']['status'];
  213. $projects[$project]['releases'] = $subscription['update']['releases'];
  214. $projects[$project]['recommended'] = $subscription['update']['recommended'];
  215. $projects[$project]['latest_version'] = $subscription['update']['latest_version'];
  216. // Security updates are a separate piece of data. If we leave it, then core
  217. // security warnings from druapl.org will also be displayed on the update page.
  218. unset($projects[$project]['security updates']);
  219. }
  220. else {
  221. $projects[$project]['status'] = UPDATE_NOT_CHECKED;
  222. $projects[$project]['reason'] = t('No information available from the Acquia Network');
  223. unset($projects[$project]['releases']);
  224. unset($projects[$project]['recommended']);
  225. }
  226. $projects[$project]['link'] = 'http://acquia.com/products-services/acquia-drupal';
  227. $projects[$project]['title'] = 'Acquia Drupal';
  228. $projects[$project]['existing_version'] = ACQUIA_DRUPAL_VERSION;
  229. $projects[$project]['install_type'] = 'official';
  230. unset($projects[$project]['extra']);
  231. }
  232. elseif ($project_info['datestamp'] == 'acquia drupal') {
  233. $projects['drupal']['includes'][$project] = $project_info['title'];
  234. unset($projects[$project]);
  235. }
  236. }
  237. }
  238. /**
  239. * Implementation of hook_system_info_alter()
  240. */
  241. function acquia_agent_system_info_alter(&$info) {
  242. if (!$subscription = acquia_agent_has_update_service()) {
  243. // Get subscription data or return if the service is not enabled.
  244. return;
  245. }
  246. if (isset($info['acquia'])) {
  247. // Slight hack - the datestamp field is carried thourgh by update.module.
  248. $info['datestamp'] = 'acquia drupal';
  249. }
  250. }
  251. /**
  252. * Returns the stored subscription data if update service is enabled or FALSE otherwise.
  253. */
  254. function acquia_agent_has_update_service() {
  255. // Include version number information.
  256. acquia_agent_load_versions();
  257. $subscription = acquia_agent_settings('acquia_subscription_data');
  258. if (!IS_ACQUIA_DRUPAL || empty($subscription['active']) || (isset($subscription['update_service']) && empty($subscription['update_service']))) {
  259. // We don't have update service if (1) this is not Acquia Drupal, (2) there
  260. // is no subscription or (3) the update service was disabled on acquia.com.
  261. // Requiring the update_service key and checking its value separately is
  262. // important for backwards compatibility. Isset & empty tells us
  263. // that the web service willingly told us to not do update notifications.
  264. return FALSE;
  265. }
  266. return $subscription;
  267. }
  268. /**
  269. * Implemetation of hook_menu_alter()
  270. */
  271. function acquia_agent_menu_alter(&$items) {
  272. if (isset($items['admin/reports/updates/check'])) {
  273. $items['admin/reports/updates/check']['page callback'] = 'acquia_agent_manual_status';
  274. }
  275. }
  276. /**
  277. * Menu callback for 'admin/config/system/acquia-agent/refresh-status'.
  278. */
  279. function acquia_agent_refresh_status() {
  280. // Refresh subscription information, so we are sure about our update status.
  281. // We send a heartbeat here so that all of our status information gets
  282. // updated locally via the return data.
  283. acquia_agent_check_subscription();
  284. // Return to the setting page (or destination)
  285. drupal_goto('admin/config/system/acquia-agent');
  286. }
  287. /**
  288. * Substituted menu callback for 'admin/reports/updates/check'.
  289. */
  290. function acquia_agent_manual_status() {
  291. // Refresh subscription information, so we are sure about our update status.
  292. // We send a heartbeat here so that all of our status information gets
  293. // updated locally via the return data.
  294. acquia_agent_check_subscription();
  295. // This callback will only ever be available if update module is active.
  296. update_manual_status();
  297. }
  298. /**
  299. * Implementation of hook_cron().
  300. */
  301. function acquia_agent_cron() {
  302. // Check subscription and send a heartbeat to Acquia Network via XML-RPC.
  303. acquia_agent_check_subscription();
  304. }
  305. /**
  306. * Implementation of hook_watchdog().
  307. */
  308. function acquia_agent_watchdog($log_entry) {
  309. // Make sure that even when cron failures prevent hook_cron() from being
  310. // called, we still send out a heartbeat.
  311. $cron_failure_messages = array(
  312. 'Cron has been running for more than an hour and is most likely stuck.',
  313. 'Attempting to re-run cron while it is already running.',
  314. );
  315. if (in_array($log_entry['message'], $cron_failure_messages, TRUE)) {
  316. acquia_agent_check_subscription();
  317. }
  318. }
  319. /**
  320. * @defgroup acquia_admin_menu Alter or add to the administration menu.
  321. * @{
  322. * The admin_menu module is enabled by default - we alter it to add our icon and
  323. * subscription information.
  324. */
  325. /**
  326. * Implementation of hook_admin_menu().
  327. */
  328. function acquia_agent_admin_menu() {
  329. // Add link to show current subscription status
  330. $links[] = array(
  331. 'title' => 'acquia_subscription_status',
  332. 'path' => 'http://acquia.com',
  333. 'weight' => -80,
  334. 'parent_path' => '<root>',
  335. 'options' => array('extra class' => 'admin-menu-action acquia-subscription-status', 'html' => TRUE),
  336. );
  337. return $links;
  338. }
  339. /**
  340. * Implements hook_help().
  341. */
  342. function acquia_agent_help($path, $arg) {
  343. switch ($path) {
  344. case 'admin/help#acquia_agent':
  345. $output = '<h2>' . t('Acquia Network and Connector modules') . '</h2>';
  346. $output .= '<p>' . t("The Acquia Network Connector suite of modules allow you to connect your site to the Acquia Network and use its variety of services.") . '<p>';
  347. $output .= '<dl>';
  348. $output .= '<dt>Acquia Agent</dt>';
  349. $output .= '<dd>' . t('Enables secure communication between your Drupal sites and the Acquia Network.') . '</dt>';
  350. $output .= '<dt>Acquia SPI</dt>';
  351. $output .= '<dd>' . t('Automates the collection of site information. Required for use with the Acquia Insight service.') . '</dt>';
  352. $output .= '<dt>Acquia Search</dt>';
  353. $output .= '<dd>' . t('Provides authentication service to the Apache Solr Search Integration module to enable use of Acquia\'s hosted Solr search indexes.') . '</dt>';
  354. $output .= '</dl>';
  355. $output .= '<h3>' . t('Configuration settings') . '</h3>';
  356. $output .= '<dl>';
  357. $output .= '<dt>' . t('Data collection and examination') . '</dt>';
  358. $output .= '<dd>' . t('Upon cron (or if configured to run manually) information about your site will be sent and analyzed as part of the Acquia Insight service. You can optionally exclude information about admin privileges, content and user count, and watchdog logs.');
  359. $output .= '<dt>' . t('Source code analysis') . '</dt>';
  360. $output .= '<dd>' . t('If enhanced SSL security is enabled for your site and outside connections are allowed, Acquia Insight will examine the source code of your site to detect alterations and provide code diffs and update recommentations.');
  361. $output .= '<dt>' . t('Enhanced SSL security') . '</dt>';
  362. $output .= '<dd>' . t('The Acquia Connector will attempt to verify Acquia server identities before sending data if SSL is avaliable. May cause communication to fail, however, depending on your local configuration.');
  363. $ssl_available = (in_array('ssl', stream_get_transports(), TRUE) && !defined('ACQUIA_DEVELOPMENT_NOSSL'));
  364. if ($ssl_available) {
  365. $output .= ' <div class="messages ok">'. t('PHP has SSL support and may support this feature.') .'</div></dd>';
  366. }
  367. else {
  368. $output .= ' <div class="messages error">'. t('Your installation of PHP does not have SSL support. Please enable the SSL extension or compile PHP with SSL to use this feature, see: <a href="http://php.net/manual/en/book.openssl.php" target="_blank">http://php.net/manual/en/book.openssl.php</a>.') .'</div></dd>';
  369. }
  370. $output .= '<dt>' . t('Receive updates from Acquia Network') . '</dt>';
  371. $output .= '<dd>' . t('Receive dynamic updates on the Network Settings page from Acquia.com about your subscription and new features.') . '</dd>';
  372. $output .= '</dl>';
  373. return $output;
  374. }
  375. }
  376. /**
  377. * Render an icon to display in the Administration Menu.
  378. */
  379. function acquia_agent_menu_icon() {
  380. return '<img class="admin-menu-icon" src="' . base_path() . drupal_get_path('module', 'acquia_agent') . '/acquia.ico" height = "16" alt="" />';
  381. }
  382. /**
  383. * @} End of "acquia_admin_menu".
  384. */
  385. /**
  386. * Validate identifier/key pair via XML-RPC call to Acquia Network address.
  387. *
  388. * This is generaly only useful when actually entering the values in the form.
  389. * Normally, use acquia_agent_check_subscription() since it also validates
  390. * the response.
  391. */
  392. function acquia_agent_valid_credentials($identifier, $key, $acquia_network_address = NULL) {
  393. $data = acquia_agent_call('acquia.agent.validate', array(), $identifier, $key, $acquia_network_address);
  394. return (bool)$data['result'];
  395. }
  396. /**
  397. * Prepare and send a XML-RPC request to Acquia Network with an authenticator.
  398. *
  399. */
  400. function acquia_agent_call($method, $params, $identifier = NULL, $key = NULL, $acquia_network_address = NULL) {
  401. $acquia_network_address = acquia_agent_network_address($acquia_network_address);
  402. $ip = isset($_SERVER["SERVER_ADDR"]) ? $_SERVER["SERVER_ADDR"] : '';
  403. $host = isset($_SERVER["HTTP_HOST"]) ? $_SERVER["HTTP_HOST"] : '';
  404. $ssl = isset($_SERVER["HTTPS"]) ? TRUE : FALSE;
  405. $data = array(
  406. 'authenticator' => _acquia_agent_authenticator($params, $identifier, $key),
  407. 'ip' => $ip,
  408. 'host' => $host,
  409. 'ssl' => $ssl,
  410. 'body' => $params,
  411. );
  412. $data['result'] = _acquia_agent_request($acquia_network_address, $method, $data);
  413. return $data;
  414. }
  415. /**
  416. * Returns an error message for the most recent (failed) attempt to connect
  417. * to the Acquia Network during the current page request. If there were no
  418. * failed attempts, returns FALSE.
  419. *
  420. * This function assumes that the most recent XML-RPC error came from the
  421. * Acquia Network; otherwise, it will not work correctly.
  422. */
  423. function acquia_agent_connection_error_message() {
  424. $errno = xmlrpc_errno();
  425. if ($errno) {
  426. switch ($errno) {
  427. case SUBSCRIPTION_NOT_FOUND:
  428. return t('The identifier you have provided does not exist in the Acquia Network or is expired. Please make sure you have used the correct value and try again.');
  429. break;
  430. case SUBSCRIPTION_EXPIRED:
  431. return t('Your Acquia Network subscription has expired. Please renew your subscription so that you can resume using Acquia Network services.');
  432. break;
  433. case SUBSCRIPTION_MESSAGE_FUTURE:
  434. return t('Your server is unable to communicate with the Acquia Network due to a problem with your clock settings. For security reasons, we reject messages that are more than @time ahead of the actual time recorded by our servers. Please fix the clock on your server and try again.', array('@time' => format_interval(SUBSCRIPTION_MESSAGE_LIFETIME)));
  435. break;
  436. case SUBSCRIPTION_MESSAGE_EXPIRED:
  437. return t('Your server is unable to communicate with the Acquia Network due to a problem with your clock settings. For security reasons, we reject messages that are more than @time older than the actual time recorded by our servers. Please fix the clock on your server and try again.', array('@time' => format_interval(SUBSCRIPTION_MESSAGE_LIFETIME)));
  438. break;
  439. case SUBSCRIPTION_VALIDATION_ERROR:
  440. return t('The identifier and key you have provided for the Acquia Network do not match. Please make sure you have used the correct values and try again.');
  441. break;
  442. default:
  443. return t('There is an error communicating with the Acquia Network at this time. Please check your identifier and key and try again.');
  444. break;
  445. }
  446. }
  447. return FALSE;
  448. }
  449. /**
  450. * Helper function to build the xmlrpc target address.
  451. */
  452. function acquia_agent_network_address($acquia_network_address = NULL) {
  453. if (empty($acquia_network_address)) {
  454. $acquia_network_address = acquia_agent_settings('acquia_network_address');
  455. }
  456. // Strip protocol (scheme) from Network address
  457. $uri = parse_url($acquia_network_address);
  458. if (isset($uri['host'])) {
  459. $acquia_network_address = $uri['host'];
  460. }
  461. $acquia_network_address .= isset($uri['port']) ? ':' . $uri['port'] : '';
  462. $acquia_network_address .= (isset($uri['path']) && isset($uri['host'])) ? $uri['path'] : '';
  463. // Add a scheme based on PHP's capacity.
  464. if (in_array('ssl', stream_get_transports(), TRUE) && !defined('ACQUIA_DEVELOPMENT_NOSSL')) {
  465. // OpenSSL is available in PHP
  466. $acquia_network_address = 'https://' . $acquia_network_address;
  467. }
  468. else {
  469. $acquia_network_address = 'http://' . $acquia_network_address;
  470. }
  471. $acquia_network_address .= '/xmlrpc.php';
  472. return $acquia_network_address;
  473. }
  474. /**
  475. * Helper function to check if an identifer and key exist.
  476. */
  477. function acquia_agent_has_credentials() {
  478. return (bool)(variable_get('acquia_identifier', FALSE) && variable_get('acquia_key', FALSE));
  479. }
  480. /**
  481. * Helper function to check if the site has an active subscription.
  482. */
  483. function acquia_agent_subscription_is_active() {
  484. $active = FALSE;
  485. // Subscription cannot be active if we have no credentials.
  486. if (acquia_agent_has_credentials()) {
  487. $subscription = acquia_agent_settings('acquia_subscription_data');
  488. // Make sure we have data at least once per day.
  489. if (isset($subscription['timestamp']) && (time() - $subscription['timestamp'] > 60*60*24)) {
  490. $subscription = acquia_agent_check_subscription(array('no_heartbeat' => 1));
  491. }
  492. $active = !empty($subscription['active']);
  493. }
  494. return $active;
  495. }
  496. /**
  497. * Helper function so that we don't need to repeat defaults.
  498. */
  499. function acquia_agent_settings($variable_name) {
  500. switch ($variable_name) {
  501. case 'acquia_identifier':
  502. return variable_get('acquia_identifier', '');
  503. case 'acquia_key':
  504. return variable_get('acquia_key', '');
  505. case 'acquia_network_address':
  506. return variable_get('acquia_network_address', 'https://rpc.acquia.com');
  507. case 'acquia_subscription_data':
  508. return variable_get('acquia_subscription_data', array('active' => FALSE));
  509. case 'acquia_subscription_name':
  510. return variable_get('acquia_subscription_name', '');
  511. }
  512. }
  513. /**
  514. * API function used by others to ensure version information is loaded.
  515. *
  516. * Saves us some cycles to not load it each time, when it is actually
  517. * not needed. We store this in a separate file, so that the Acquia
  518. * build process only needs to alter that file instead of the main
  519. * module file.
  520. */
  521. function acquia_agent_load_versions() {
  522. // Include version number information.
  523. include_once 'acquia_agent_drupal_version.inc';
  524. }
  525. /**
  526. * Implementation of hook_form_[form_id]_alter()..
  527. */
  528. function acquia_agent_form_system_modules_alter(&$form, &$form_state) {
  529. if (isset($form['description']['acquia_search'])) {
  530. $subscription = acquia_agent_settings('acquia_subscription_data');
  531. if (!module_exists('acquia_search') && empty($subscription['active'])) {
  532. $form['status']['#disabled_modules'][] = 'acquia_search';
  533. $text = 'Acquia Search requires an <a href="@network-url">Acquia Network subscription</a>';
  534. $message = t($text, array('@network-url' => 'http://acquia.com/products-services/acquia-search'));
  535. $form['description']['acquia_search']['#value'] = '<div style="padding-left:5px; margin:8px 0px" class="messages warning" id="acquia-agent-no-search">' . $message . '</div>' . $form['description']['acquia_search']['#value'];
  536. }
  537. }
  538. }
  539. /**
  540. * Builds a stream context based on a url and local .pem file if available.
  541. */
  542. function acquia_agent_stream_context_create($url, $module = 'acquia_agent') {
  543. $opts = array();
  544. $uri = parse_url($url);
  545. $ssl_available = in_array('ssl', stream_get_transports(), TRUE) && !defined('ACQUIA_DEVELOPMENT_NOSSL') && variable_get('acquia_agent_verify_peer', 1);
  546. if (isset($uri['scheme']) && ($uri['scheme'] == 'https') && $ssl_available) {
  547. // Look for a local certificate to validate the server identity.
  548. $pem_file = drupal_get_path('module', $module) . '/' . $uri['host'] . '.pem';
  549. if (file_exists($pem_file)) {
  550. $opts['ssl'] = array(
  551. 'verify_peer' => TRUE,
  552. 'cafile' => $pem_file,
  553. 'allow_self_signed' => FALSE, // doesn't mean anything in this case
  554. 'CN_match' => $uri['host']);
  555. }
  556. }
  557. return stream_context_create($opts);
  558. }
  559. /**
  560. * Determine if a response from the Acquia Network is valid.
  561. *
  562. * @param $data
  563. * The data array returned by acquia_agent_call().
  564. * @return
  565. * TRUE or FALSE.
  566. */
  567. function acquia_agent_valid_response($data) {
  568. $authenticator = $data['authenticator'];
  569. $result = $data['result'];
  570. $result_auth = $result['authenticator'];
  571. $valid = ($authenticator['nonce'] == $result_auth['nonce']);
  572. $valid = $valid && ($authenticator['time'] < $result_auth['time']);
  573. $key = acquia_agent_settings('acquia_key');
  574. $hash = _acquia_agent_hmac($key, $result_auth['time'], $result_auth['nonce'], $result['body']);
  575. return $valid && ($hash == $result_auth['hash']);
  576. }
  577. /**
  578. * Send a XML-RPC request.
  579. *
  580. * This function should never be called directly - use acquia_agent_call().
  581. */
  582. function _acquia_agent_request($url, $method, $data) {
  583. $ctx = acquia_agent_stream_context_create($url);
  584. if (!$ctx) {
  585. // TODO: what's a meaningful fault code?
  586. xmlrpc_error(-1, t('SSL is not supported or setup failed'));
  587. $result = FALSE;
  588. }
  589. else {
  590. $result = xmlrpc($url, array($method => array($data)), array('context' => $ctx));
  591. }
  592. if ($errno = xmlrpc_errno()) {
  593. watchdog('acquia agent', '@message (@errno): %server - %method - <pre>@data</pre>', array('@message' => xmlrpc_error_msg(), '@errno' => xmlrpc_errno(), '%server' => $url, '%method' => $method, '@data' => print_r($data, TRUE)), WATCHDOG_ERROR);
  594. }
  595. return $result;
  596. }
  597. /**
  598. * Creates an authenticator based on xmlrpc params and a HMAC-SHA1.
  599. */
  600. function _acquia_agent_authenticator($params = array(), $identifier = NULL, $key = NULL) {
  601. if (empty($identifier)) {
  602. $identifier = acquia_agent_settings('acquia_identifier');
  603. }
  604. if (empty($key)) {
  605. $key = acquia_agent_settings('acquia_key');
  606. }
  607. $time = REQUEST_TIME;
  608. $nonce = base64_encode(hash('sha256', drupal_random_bytes(55), TRUE));
  609. $authenticator['identifier'] = $identifier;
  610. $authenticator['time'] = $time;
  611. $authenticator['hash'] = _acquia_agent_hmac($key, $time, $nonce, $params);
  612. $authenticator['nonce'] = $nonce;
  613. return $authenticator;
  614. }
  615. /**
  616. * Calculates a HMAC-SHA1 according to RFC2104 (http://www.ietf.org/rfc/rfc2104.txt).
  617. * With addition of xmlrpc params.
  618. */
  619. function _acquia_agent_hmac($key, $time, $nonce, $params) {
  620. if (empty($params['rpc_version']) || $params['rpc_version'] < 2) {
  621. $encoded_params = serialize($params);
  622. $string = $time .':'. $nonce .':'. $key .':'. $encoded_params;
  623. return base64_encode(
  624. pack("H*", sha1((str_pad($key, 64, chr(0x00)) ^ (str_repeat(chr(0x5c), 64))) .
  625. pack("H*", sha1((str_pad($key, 64, chr(0x00)) ^ (str_repeat(chr(0x36), 64))) .
  626. $string)))));
  627. }
  628. elseif ($params['rpc_version'] == 2) {
  629. $encoded_params = json_encode($params);
  630. $string = $time .':'. $nonce .':'. $encoded_params;
  631. return sha1((str_pad($key, 64, chr(0x00)) ^ (str_repeat(chr(0x5c), 64))) . pack("H*", sha1((str_pad($key, 64, chr(0x00)) ^ (str_repeat(chr(0x36), 64))) . $string)));
  632. }
  633. else {
  634. $string = $time .':'. $nonce;
  635. return sha1((str_pad($key, 64, chr(0x00)) ^ (str_repeat(chr(0x5c), 64))) . pack("H*", sha1((str_pad($key, 64, chr(0x00)) ^ (str_repeat(chr(0x36), 64))) . $string)));
  636. }
  637. }
  638. /**
  639. * Creates an authenticator for XML-RPC calls without Network identifier or key.
  640. *
  641. * @param array $body array of values being sent to remote server
  642. * @param string $pass
  643. */
  644. function _acquia_agent_create_authenticator($body, $pass = NULL) {
  645. $auth = array();
  646. $auth['time'] = REQUEST_TIME;
  647. $auth['nonce'] = base64_encode(hash('sha256', drupal_random_bytes(55), TRUE));
  648. if (isset($pass)) {
  649. $auth['hash'] = _acquia_agent_hmac($pass, $auth['time'], $auth['nonce'], $body);
  650. }
  651. else {
  652. // XML-RPC interface requires this parameter to be a string.
  653. // Just pass a dummy value.
  654. $auth['hash'] = 'x';
  655. }
  656. return $auth;
  657. }