hosting_ssl.nodeapi.inc

NodeAPI functions for the Hosting SSL module.

Functions

Namesort descending Description
hosting_ssl_clean_keys Remove unused SSL keys from the system (but not from the backend).
hosting_ssl_filter_key Filter disallowed characters from a ssl certificate key.
hosting_ssl_get_ip Get an Array of all IP's associated with an SSL certificate.
hosting_ssl_get_key @todo Please document this function.
hosting_ssl_get_keys Retrieve an associated array of possible keys.
hosting_ssl_hosting_site_options_alter Implemensts hook_hosting_site_options_alter
hosting_ssl_nodeapi_site_delete Implements hook_nodeapi_TYPE_OP().
hosting_ssl_nodeapi_site_delete_revision Implements hook_nodeapi_TYPE_OP().
hosting_ssl_nodeapi_site_insert Implements hook_nodeapi_TYPE_OP().
hosting_ssl_nodeapi_site_presave Implements hook_nodeapi_TYPE_OP().
hosting_ssl_nodeapi_site_update Implements hook_nodeapi_TYPE_OP().
hosting_ssl_nodeapi_site_validate Implements hook_nodeapi_TYPE_OP().
hosting_ssl_nodeapi_site_view Implements hook_nodeapi_TYPE_OP().
hosting_ssl_node_load Implements hook_node_load().
hosting_ssl_output_key @todo Please document this function.
hosting_ssl_save_key Store the SSL Cert key in the database.
hosting_ssl_site_form Form API code to extend the site form with SSL fields.
hosting_ssl_status_options Output filter for SSL enabled field.

Constants

Namesort descending Description
HOSTING_SSL_CUSTOM_KEY This SSL key is a custom, Aegir generated one.

File

web_server/ssl/hosting_ssl.nodeapi.inc
View source
  1. <?php
  2. /**
  3. * @file
  4. * NodeAPI functions for the Hosting SSL module.
  5. */
  6. /**
  7. * This SSL key is a custom, Aegir generated one.
  8. */
  9. define('HOSTING_SSL_CUSTOM_KEY', 'null');
  10. /**
  11. * Form API code to extend the site form with SSL fields.
  12. */
  13. function hosting_ssl_site_form(&$form, &$form_state, $form_id) {
  14. $node = $form['#node'];
  15. $new_site = TRUE;
  16. $ssl_available = FALSE;
  17. // Only allow the user to modify these values when the platform is SSL enabled.
  18. if (isset($node->nid)) {
  19. $new_site = FALSE;
  20. $platform = node_load($node->platform);
  21. $server = node_load($platform->web_server);
  22. if ($server->services['http']->ssl_enabled) {
  23. $ssl_available = TRUE;
  24. }
  25. }
  26. _hosting_site_field($form, $node, 'hosting_ssl_wrapper', array(
  27. '#type' => 'fieldset',
  28. '#title' => t('SSL Settings'),
  29. '#default_value' => NULL,
  30. ), 'filter_xss', $ssl_available);
  31. _hosting_site_field($form['hosting_ssl_wrapper'], $node, 'ssl_enabled', array(
  32. '#type' => 'radios',
  33. '#title' => t('Encryption'),
  34. '#options' => hosting_ssl_status_options(),
  35. '#description' => t('Enabling encryption will publish your site on both HTTP and HTTPS ports, allowing you to redirect users to the more secure version for certain pages that require the additional security. Requiring encryption will automatically redirect all unencrypted traffic to your HTTPS site.'),
  36. '#required' => TRUE,
  37. '#default_value' => (isset($node->ssl_enabled)) ? $node->ssl_enabled : HOSTING_SSL_DISABLED,
  38. '#access' => user_access('create ssl certificate'),
  39. ), 'hosting_ssl_status_options', $ssl_available);
  40. _hosting_site_field($form['hosting_ssl_wrapper'], $node, 'ssl_key', array(
  41. '#type' => 'radios',
  42. '#title' => t('Encryption key'),
  43. '#description' => t("Choose an existing SSL certificate. If you do not wish to use any of your existing certificates, you may choose to generate a new one."),
  44. '#options' => hosting_ssl_get_keys(NULL, TRUE),
  45. '#required' => TRUE,
  46. '#default_value' => (isset($node->ssl_key) && $node->ssl_key > 0) ? $node->ssl_key : HOSTING_SSL_CUSTOM_KEY,
  47. '#access' => user_access('create ssl certificate'),
  48. '#states' => array(
  49. 'visible' => array(
  50. ':input[name="ssl_enabled"]' => array('!value' => 0),
  51. ),
  52. ),
  53. ), 'hosting_ssl_output_key', $ssl_available, !$new_site);
  54. _hosting_site_field($form['hosting_ssl_wrapper'], $node, 'ssl_key_new', array(
  55. '#type' => 'textfield',
  56. '#title' => t('New encryption key'),
  57. '#description' => t("A name for the certificate, often relating to the domain name. This field should only contain lower case alpha-numeric and '_', '-' or '.' characters. If the SSL certificate is not found in config/ssl.d, Aegir will automatically generate a self-signed certificate for you. You can replace the generated with a properly signed version later. Any required intermediate certificates can be added in a file called config/ssl.d/&lt;name&gt;/openssl_chain.crt"),
  58. '#default_value' => '',
  59. '#access' => user_access('create ssl certificate'),
  60. '#states' => array(
  61. 'visible' => array(
  62. ':input[name="ssl_enabled"]' => array('!value' => 0),
  63. ':input[name="ssl_key"]' => array('value' => HOSTING_SSL_CUSTOM_KEY),
  64. ),
  65. ),
  66. ), 'filter_xss', $ssl_available, FALSE);
  67. }
  68. /**
  69. * Implemensts hook_hosting_site_options_alter
  70. */
  71. function hosting_ssl_hosting_site_options_alter(&$return, $node) {
  72. // Disable the ssl key fields by default.
  73. if (!count(hosting_ssl_get_servers())) {
  74. $return['ssl_enabled'] = FALSE;
  75. }
  76. $return['ssl_key'] = FALSE;
  77. $return['ssl_key_new'] = FALSE;
  78. // Test if ssl has been enabled.
  79. //if (isset($node->ssl_enabled)) {
  80. if (isset($node->ssl_enabled) && $node->ssl_enabled) {
  81. $keys = hosting_ssl_get_keys($node->client, TRUE);
  82. // Return the list of valid keys, including the special 'new key' option.
  83. $return['ssl_key'] = array_keys($keys);
  84. // Properly default this value so things dont fall apart later.
  85. if (count($return['ssl_key']) == 1) {
  86. $node->ssl_key = HOSTING_SSL_CUSTOM_KEY;
  87. }
  88. // the user has chosen to enter a new key
  89. if ($node->ssl_key == HOSTING_SSL_CUSTOM_KEY) {
  90. // default the new key to the site's domain name, after filtering.
  91. $default = hosting_ssl_filter_key($node->title);
  92. $return['ssl_key_new'] = (!empty($default)) ? $default : TRUE;
  93. }
  94. // we need to ensure that the return value is properly indexed, otherwise it
  95. // gets interpreted as an object by jquery.
  96. $return['profile'] = array_values(array_intersect($return['profile'], hosting_ssl_get_profiles()));
  97. $return['platform'] = array_values(array_intersect($return['platform'], hosting_ssl_get_platforms()));
  98. }
  99. }
  100. /**
  101. * Implements hook_nodeapi_TYPE_OP().
  102. */
  103. function hosting_ssl_nodeapi_site_view(&$node, $teaser = FALSE) {
  104. $node->content['info']['ssl_enabled'] = array(
  105. '#type' => 'item',
  106. '#title' => t('Encryption'),
  107. '#markup' => hosting_ssl_status_options((isset($node->ssl_enabled)) ? $node->ssl_enabled : HOSTING_SSL_DISABLED),
  108. '#weight' => 6,
  109. );
  110. if (isset($node->ssl_enabled) && $node->ssl_enabled == TRUE) {
  111. $node->content['info']['ssl_key'] = array(
  112. '#type' => 'item',
  113. '#title' => t('Encryption key'),
  114. '#value' => hosting_ssl_output_key($node->ssl_key),
  115. '#description' => hosting_ssl_get_key($node->ssl_key) ? t("This site is using SSL certificates located at %path.", array('%path' => sprintf("config/ssl.d/%s/", hosting_ssl_output_key($node->ssl_key)))) : t("(key deleted)"),
  116. '#weight' => 7,
  117. );
  118. }
  119. }
  120. /**
  121. * @todo Please document this function.
  122. * @see http://drupal.org/node/1354
  123. */
  124. function hosting_ssl_get_key($cid) {
  125. static $cache = array();
  126. if (!isset($cache[$cid])) {
  127. $cache[$cid] = db_query("SELECT ssl_key FROM {hosting_ssl_cert} WHERE cid = :cid", array(':cid' => $cid))->fetchField();
  128. }
  129. return $cache[$cid];
  130. }
  131. /**
  132. * Get an Array of all IP's associated with an SSL certificate.
  133. */
  134. function hosting_ssl_get_ip($cid) {
  135. $result = db_query("SELECT h.name AS server_name, ips.ip_address
  136. FROM {hosting_ssl_cert_ips} cert
  137. INNER JOIN {hosting_ip_addresses} ips
  138. ON cert.ip_address = ips.id
  139. INNER JOIN {hosting_context} h
  140. ON h.nid = ips.nid
  141. WHERE cid = :cid",
  142. array(':cid' => $cid));
  143. $ip_addresses = array();
  144. foreach ($result as $record) {
  145. $ip_addresses[$record->server_name] = $record->ip_address;
  146. }
  147. return $ip_addresses;
  148. }
  149. /**
  150. * @todo Please document this function.
  151. * @see http://drupal.org/node/1354
  152. */
  153. function hosting_ssl_output_key($cid) {
  154. return filter_xss(hosting_ssl_get_key($cid));
  155. }
  156. /**
  157. * Output filter for SSL enabled field.
  158. */
  159. function hosting_ssl_status_options($status = NULL) {
  160. $options = array(
  161. HOSTING_SSL_DISABLED => t('Disabled'),
  162. HOSTING_SSL_ENABLED => t('Enabled'),
  163. HOSTING_SSL_REQUIRED => t('Required'),
  164. );
  165. if (!is_null($status)) {
  166. return $options[$status];
  167. }
  168. return $options;
  169. }
  170. /**
  171. * Filter disallowed characters from a ssl certificate key.
  172. *
  173. * Only lowercase alphanumeric- and '.', '_' or '-' characters are allowed for ssl keys.
  174. */
  175. function hosting_ssl_filter_key($key) {
  176. return strtolower(preg_replace("/[^\w\.\-]/", "", $key));
  177. }
  178. /**
  179. * Implements hook_nodeapi_TYPE_OP().
  180. */
  181. function hosting_ssl_nodeapi_site_validate($node, &$form) {
  182. if ($node->ssl_enabled) {
  183. // TODO: find a way to avoid calling this function multiple times in hook_validate
  184. $valid_options = hosting_site_available_options($node);
  185. if ($node->ssl_key == HOSTING_SSL_CUSTOM_KEY) {
  186. if (!strlen($node->ssl_key_new)) {
  187. form_set_error('ssl_key_new', t("The encryption key field is required to enable us to generate a new SSL certificate for your site."));
  188. }
  189. else {
  190. $key = hosting_ssl_filter_key($node->ssl_key_new);
  191. if (($node->ssl_key_new != $key) || !strlen($key)) {
  192. form_set_error('ssl_key_new', t("The encryption key field should only contain lower case alpha-numeric and '_', '-' or '.' characters."));
  193. }
  194. if (!ctype_alnum($key[0])) {
  195. form_set_error('ssl_key_new', t("The encryption key field must start with an alpha-numeric character."));
  196. }
  197. if ($key == HOSTING_SSL_CUSTOM_KEY) {
  198. form_set_error('ssl_key_new', t("This encryption key value is reserved for internal use, please choose another"));
  199. }
  200. }
  201. }
  202. else {
  203. if (!in_array($node->ssl_key, $valid_options['ssl_key'])) {
  204. form_set_error('ssl_key', t("You have chosen an invalid SSL key"));
  205. }
  206. }
  207. // TODO, check that the server to host this site has ssl enabled.
  208. // if (!empty($node->platform)) {
  209. // $platform = node_load($node->platform);
  210. // $server = node_load($platform->web_server);
  211. // $server->services['http']->type == 'apache_ssl' or nginx_ssl???
  212. // }
  213. }
  214. }
  215. /**
  216. * Implements hook_nodeapi_TYPE_OP().
  217. */
  218. function hosting_ssl_nodeapi_site_presave(&$node) {
  219. if (!isset($node->ssl_key)) {
  220. $node->ssl_key = HOSTING_SSL_CUSTOM_KEY;
  221. }
  222. if (!isset($node->ssl_enabled)) {
  223. $node->ssl_enabled = HOSTING_SSL_DISABLED;
  224. }
  225. // This creates the SSL key for the site, if required.
  226. $node->ssl_key = hosting_ssl_save_key($node);
  227. }
  228. /**
  229. * Implements hook_nodeapi_TYPE_OP().
  230. */
  231. function hosting_ssl_nodeapi_site_insert($node) {
  232. $id = db_insert('hosting_ssl_site')
  233. ->fields(array(
  234. 'vid' => $node->vid,
  235. 'nid' => $node->nid,
  236. 'ssl_enabled' => $node->ssl_enabled,
  237. 'ssl_key' => $node->ssl_key,
  238. ))
  239. ->execute();
  240. }
  241. /**
  242. * Implements hook_nodeapi_TYPE_OP().
  243. */
  244. function hosting_ssl_nodeapi_site_update($node) {
  245. // check if an existing record is there
  246. $result = db_query("SELECT ssl_enabled FROM {hosting_ssl_site} WHERE vid = :vid", array(':vid' => $node->vid));
  247. if (!($obj = $result->fetch())) {
  248. hosting_ssl_nodeapi_site_insert($node);
  249. }
  250. else {
  251. if ($node->site_status == HOSTING_SITE_DELETED || (!$node->ssl_enabled && $node->ssl_key != 0)) {
  252. hosting_ssl_clean_keys($node);
  253. }
  254. db_update('hosting_ssl_site')
  255. ->fields(array(
  256. 'ssl_enabled' => $node->ssl_enabled,
  257. 'ssl_key' => $node->ssl_key,
  258. ))
  259. ->condition('vid', $node->vid)
  260. ->execute();
  261. }
  262. }
  263. /**
  264. * Implements hook_node_load().
  265. */
  266. function hosting_ssl_node_load($nodes, $types) {
  267. foreach ($nodes as $k => $node) {
  268. $result = db_query("SELECT ssl_enabled, ssl_key FROM {hosting_ssl_site} WHERE vid = :vid",
  269. array(':vid' => $node->vid)
  270. )->fetchObject();
  271. if ($result) {
  272. $nodes[$k]->ssl_enabled = $result->ssl_enabled;
  273. $nodes[$k]->ssl_key = $result->ssl_key;
  274. }
  275. }
  276. }
  277. /**
  278. * Implements hook_nodeapi_TYPE_OP().
  279. */
  280. function hosting_ssl_nodeapi_site_delete($node) {
  281. hosting_ssl_clean_keys($node);
  282. db_delete('hosting_ssl_site')
  283. ->condition('nid', $node->nid)
  284. ->execute();
  285. }
  286. /**
  287. * Implements hook_nodeapi_TYPE_OP().
  288. */
  289. function hosting_ssl_nodeapi_site_delete_revision($node) {
  290. db_delete('hosting_ssl_site')
  291. ->condition('vid', $node->vid)
  292. ->execute();
  293. }
  294. /**
  295. * Store the SSL Cert key in the database.
  296. */
  297. function hosting_ssl_save_key($node) {
  298. if (empty($node->ssl_enabled)) {
  299. return 0;
  300. }
  301. $client = hosting_get_client($node->client);
  302. if ($node->ssl_key == HOSTING_SSL_CUSTOM_KEY && !empty($node->ssl_key_new)) {
  303. $ssl_key = $node->ssl_key_new;
  304. $result = db_query("SELECT * FROM {hosting_ssl_cert} WHERE ssl_key = :ssl_key", array(':ssl_key' => $ssl_key));
  305. if ($obj = $result->fetch()) {
  306. // update
  307. if ($node->client != NULL) {
  308. $obj->client = $client->nid;
  309. }
  310. drupal_write_record("hosting_ssl_cert", $obj, 'cid');
  311. $node->ssl_key = $obj->cid;
  312. }
  313. else {
  314. // insert
  315. $obj = new stdClass();
  316. $obj->ssl_key = $ssl_key;
  317. $obj->client = $client->nid;
  318. $obj->status = 0;
  319. drupal_write_record("hosting_ssl_cert", $obj);
  320. if (!hosting_ip_allocate($obj, $node)) {
  321. form_set_error('ssl_key_new', t("Unable to allocate IP address for certificate, assuming SNI (Server Name Indication) will work (incompatible with Safari and IE on Windows XP, Android 2.2, etc)."));
  322. }
  323. $node->ssl_key = $obj->cid;
  324. }
  325. }
  326. return $node->ssl_key;
  327. }
  328. /**
  329. * Retrieve an associated array of possible keys.
  330. *
  331. * @param $client
  332. * The client to filter the keys by.
  333. * @param $ask_custom
  334. * Include the special 'generate new key' value used by the site form.
  335. *
  336. * @return array
  337. */
  338. function hosting_ssl_get_keys($client = NULL, $ask_custom = FALSE) {
  339. $keys = array();
  340. if ($ask_custom == TRUE) {
  341. $keys[HOSTING_SSL_CUSTOM_KEY] = t("Generate a new encryption key.");
  342. }
  343. $args = array();
  344. $query = "SELECT cid, ssl_key FROM {hosting_ssl_cert}";
  345. if (!is_null($client)) {
  346. $client = hosting_get_client($client);
  347. if ($client) {
  348. $query .= " WHERE client = :client";
  349. $args[':client'] = $client->nid;
  350. }
  351. }
  352. $result = db_query($query, $args);
  353. while ($obj = $result->fetchObject()) {
  354. if (count($obj->ssl_key)) {
  355. $keys[$obj->cid] = $obj->ssl_key;
  356. }
  357. }
  358. return $keys;
  359. }
  360. /**
  361. * Remove unused SSL keys from the system (but not from the backend).
  362. *
  363. * This is designed to be ran on a site's delete task or the site node's deletion.
  364. */
  365. function hosting_ssl_clean_keys($node) {
  366. // Check if there are still sites using this site's certificate.
  367. if (!db_query("SELECT * FROM {hosting_ssl_site} siteA
  368. INNER JOIN {hosting_ssl_site} siteB ON siteA.ssl_key = siteB.ssl_key
  369. INNER JOIN {hosting_site} s ON s.nid = siteA.nid
  370. WHERE siteA.nid <> siteB.nid
  371. AND siteA.ssl_enabled = :siteA_ssl_enabled
  372. AND siteB.nid = :siteB_nid;",
  373. array(
  374. ':siteA_ssl_enabled' => 1,
  375. ':siteB_nid' => $node->nid,
  376. ))->fetchField()) {
  377. // We need to fetch the ssl_key field from the DB because the object comes
  378. // from the node edit, so it's gone.
  379. $ssl_key = db_query('SELECT ssl_key FROM {hosting_ssl_site} WHERE nid = :nid',
  380. array(
  381. ':nid' => $node->nid,
  382. )
  383. )->fetchField();
  384. drupal_set_message(t("cleaning up unused certificate %cert associated with site %site", array('%cert' => $ssl_key, '%site' => $node->nid)));
  385. db_delete('hosting_ssl_cert')
  386. ->condition('cid', $ssl_key)
  387. ->execute();
  388. db_delete('hosting_ssl_cert_ips')
  389. ->condition('cid', $ssl_key)
  390. ->execute();
  391. }
  392. }