diff --git a/modules/node/node.api.php b/modules/node/node.api.php
index 3a737425bbed1f723bc0bf6ddd123c0d00e13e7c..b4c18118c54472adcf58244909d57614f4bc9c6d 100644
--- a/modules/node/node.api.php
+++ b/modules/node/node.api.php
@@ -649,10 +649,12 @@ function hook_node_update_index($node) {
  *   The node being validated.
  * @param $form
  *   The form being used to edit the node.
+ * @param $form_state
+ *   The form state array.
  *
  * @ingroup node_api_hooks
  */
-function hook_node_validate($node, $form) {
+function hook_node_validate($node, $form, &$form_state) {
   if (isset($node->end) && isset($node->start)) {
     if ($node->start > $node->end) {
       form_set_error('time', t('An event may not end before it starts.'));
@@ -973,7 +975,8 @@ function hook_prepare($node) {
  * @param $node
  *   The node being added or edited.
  * @param $form_state
- *   The form state array. Changes made to this variable will have no effect.
+ *   The form state array.
+ *
  * @return
  *   An array containing the form elements to be displayed in the node
  *   edit form.
@@ -987,7 +990,7 @@ function hook_prepare($node) {
  *
  * @ingroup node_api_hooks
  */
-function hook_form($node, $form_state) {
+function hook_form($node, &$form_state) {
   $type = node_type_get_type($node);
 
   $form['field1'] = array(
@@ -1112,10 +1115,12 @@ function hook_update($node) {
  *   The node being validated.
  * @param $form
  *   The form being used to edit the node.
+ * @param $form_state
+ *   The form state array.
  *
  * @ingroup node_api_hooks
  */
-function hook_validate($node, &$form) {
+function hook_validate($node, $form, &$form_state) {
   if (isset($node->end) && isset($node->start)) {
     if ($node->start > $node->end) {
       form_set_error('time', t('An event may not end before it starts.'));
diff --git a/modules/node/node.module b/modules/node/node.module
index fb8352de32d277ccabd527e685d643763a8958d4..8bacba7a5b7a9d99ed4341bf20e7d55617ddcbca 100644
--- a/modules/node/node.module
+++ b/modules/node/node.module
@@ -926,7 +926,7 @@ function node_object_prepare($node) {
 /**
  * Perform validation checks on the given node.
  */
-function node_validate($node, $form = array()) {
+function node_validate($node, $form, &$form_state) {
   $type = node_type_get_type($node);
 
   if (isset($node->nid) && (node_last_changed($node->nid) > $node->changed)) {
@@ -946,9 +946,18 @@ function node_validate($node, $form = array()) {
     form_set_error('date', t('You have to specify a valid date.'));
   }
 
-  // Do node-type-specific validation checks.
-  node_invoke($node, 'validate', $form);
-  module_invoke_all('node_validate', $node, $form);
+  // Invoke hook_validate() for node type specific validation and
+  // hook_node_validate() for miscellaneous validation needed by modules. Can't
+  // use node_invoke() or module_invoke_all(), because $form_state must be
+  // receivable by reference.
+  $function = node_type_get_base($node) . '_validate';
+  if (function_exists($function)) {
+    $function($node, $form, $form_state);
+  }
+  foreach (module_implements('node_validate') as $module) {
+    $function = $module . '_node_validate';
+    $function($node, $form, $form_state);
+  }
 }
 
 /**
diff --git a/modules/node/node.pages.inc b/modules/node/node.pages.inc
index fe9eff1551fffe7cb204dd5eadaa01a55de44f5a..4053b6dc520b48d48dd81b73f6284a36e54faa27 100644
--- a/modules/node/node.pages.inc
+++ b/modules/node/node.pages.inc
@@ -80,10 +80,7 @@ function node_form_validate($form, &$form_state) {
   // not update it with form values that have not yet been validated, so we
   // create a pseudo-entity to use during validation.
   $node = (object) $form_state['values'];
-  node_validate($node, $form);
-
-  // Field validation. Requires access to $form_state, so this cannot be
-  // done in node_validate() as it currently exists.
+  node_validate($node, $form, $form_state);
   entity_form_field_validate('node', $form, $form_state);
 }
 
@@ -137,8 +134,10 @@ function node_form($form, &$form_state, $node) {
     '#type' => 'hidden',
     '#default_value' => isset($node->changed) ? $node->changed : NULL,
   );
-  // Get the node-specific bits.
-  if ($extra = node_invoke($node, 'form', $form_state)) {
+  // Invoke hook_form() to get the node-specific bits. Can't use node_invoke(),
+  // because hook_form() needs to be able to receive $form_state by reference.
+  $function = node_type_get_base($node) . '_form';
+  if (function_exists($function) && ($extra = $function($node, $form_state))) {
     $form = array_merge_recursive($form, $extra);
   }
   if (!isset($form['title']['#weight'])) {