/*****************************************************************************\
 *  $Id: ipmipower_powercmd.c,v 1.206 2010-08-03 00:10:59 chu11 Exp $
 *****************************************************************************
 *  Copyright (C) 2007-2015 Lawrence Livermore National Security, LLC.
 *  Copyright (C) 2003-2007 The Regents of the University of California.
 *  Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).
 *  Written by Albert Chu <chu11@llnl.gov>
 *  UCRL-CODE-155698
 *
 *  This file is part of Ipmipower, a remote power control utility.
 *  For details, see http://www.llnl.gov/linux/.
 *
 *  Ipmipower is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by the
 *  Free Software Foundation; either version 3 of the License, or (at your
 *  option) any later version.
 *
 *  Ipmipower is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 *  or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 *  for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with Ipmipower.  If not, see <http://www.gnu.org/licenses/>.
\*****************************************************************************/

#if HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

#include <stdio.h>
#include <stdlib.h>
#if STDC_HEADERS
#include <string.h>
#endif /* STDC_HEADERS */
#if HAVE_STRINGS_H
#include <strings.h>
#endif /* HAVE_STRINGS_H */
#if HAVE_UNISTD_H
#include <unistd.h>
#endif /* HAVE_UNISTD_H */
#if TIME_WITH_SYS_TIME
#include <sys/time.h>
#include <time.h>
#else  /* !TIME_WITH_SYS_TIME */
#if HAVE_SYS_TIME_H
#include <sys/time.h>
#else /* !HAVE_SYS_TIME_H */
#include <time.h>
#endif  /* !HAVE_SYS_TIME_H */
#endif /* !TIME_WITH_SYS_TIME */
#include <netinet/in.h>
#include <assert.h>
#include <errno.h>

#include "ipmipower.h"
#include "ipmipower_connection.h"
#include "ipmipower_error.h"
#include "ipmipower_oem.h"
#include "ipmipower_output.h"
#include "ipmipower_powercmd.h"
#include "ipmipower_packet.h"
#include "ipmipower_check.h"
#include "ipmipower_util.h"

#include "freeipmi-portability.h"
#include "cbuf.h"
#include "list.h"
#include "secure.h"
#include "timeval.h"

extern struct ipmipower_arguments cmd_args;

/* Queue of all pending power commands */
static List pending = NULL;

/* Queue of power commands to be added to the pending, for serializing
 * OEM power control to the same host
 */
static List add_to_pending = NULL;

/* Count of currently executing power commands for fanout */
static unsigned int executing_count = 0;

static int
_find_ipmipower_powercmd (void *x, void *key)
{
  ipmipower_powercmd_t ip;
  char *hostname;

  assert (x);
  assert (key);

  ip = (ipmipower_powercmd_t)x;
  hostname = (char *)key;

  return (!strcasecmp (ip->ic->hostname, hostname));
}

static void
_destroy_ipmipower_powercmd (void *x)
{
  ipmipower_powercmd_t ip;

  assert (x);

  ip = (ipmipower_powercmd_t)x;

  fiid_obj_destroy (ip->obj_rmcp_hdr_rq);
  fiid_obj_destroy (ip->obj_rmcp_hdr_rs);
  fiid_obj_destroy (ip->obj_lan_session_hdr_rq);
  fiid_obj_destroy (ip->obj_lan_session_hdr_rs);
  fiid_obj_destroy (ip->obj_lan_msg_hdr_rq);
  fiid_obj_destroy (ip->obj_lan_msg_hdr_rs);
  fiid_obj_destroy (ip->obj_lan_msg_trlr_rs);
  fiid_obj_destroy (ip->obj_rmcpplus_session_hdr_rq);
  fiid_obj_destroy (ip->obj_rmcpplus_session_hdr_rs);
  fiid_obj_destroy (ip->obj_rmcpplus_payload_rs);
  fiid_obj_destroy (ip->obj_rmcpplus_session_trlr_rq);
  fiid_obj_destroy (ip->obj_rmcpplus_session_trlr_rs);
  fiid_obj_destroy (ip->obj_authentication_capabilities_rq);
  fiid_obj_destroy (ip->obj_authentication_capabilities_rs);
  fiid_obj_destroy (ip->obj_get_session_challenge_rq);
  fiid_obj_destroy (ip->obj_get_session_challenge_rs);
  fiid_obj_destroy (ip->obj_activate_session_rq);
  fiid_obj_destroy (ip->obj_activate_session_rs);
  fiid_obj_destroy (ip->obj_open_session_rq);
  fiid_obj_destroy (ip->obj_open_session_rs);
  fiid_obj_destroy (ip->obj_rakp_message_1_rq);
  fiid_obj_destroy (ip->obj_rakp_message_2_rs);
  fiid_obj_destroy (ip->obj_rakp_message_3_rq);
  fiid_obj_destroy (ip->obj_rakp_message_4_rs);
  fiid_obj_destroy (ip->obj_set_session_privilege_level_rq);
  fiid_obj_destroy (ip->obj_set_session_privilege_level_rs);
  fiid_obj_destroy (ip->obj_get_chassis_status_rq);
  fiid_obj_destroy (ip->obj_get_chassis_status_rs);
  fiid_obj_destroy (ip->obj_chassis_control_rq);
  fiid_obj_destroy (ip->obj_chassis_control_rs);
  fiid_obj_destroy (ip->obj_chassis_identify_rq);
  fiid_obj_destroy (ip->obj_chassis_identify_rs);

  if (cmd_args.oem_power_type != IPMIPOWER_OEM_POWER_TYPE_NONE)
    {
      if (cmd_args.oem_power_type == IPMIPOWER_OEM_POWER_TYPE_C410X)
        {
          fiid_obj_destroy (ip->obj_c410x_get_sensor_reading_rq);
          fiid_obj_destroy (ip->obj_c410x_get_sensor_reading_rs);
          fiid_obj_destroy (ip->obj_c410x_slot_power_control_rq);
          fiid_obj_destroy (ip->obj_c410x_slot_power_control_rs);
        }
    }

  fiid_obj_destroy (ip->obj_close_session_rq);
  fiid_obj_destroy (ip->obj_close_session_rs);

  /* Close all sockets that were saved during the Get Session
   * Challenge phase of the IPMI protocol.
   */
  if (list_count (ip->sockets_to_close) > 0)
    {
      int *fd;
      while ((fd = list_pop (ip->sockets_to_close)))
        {
          /* cleanup path, ignore potential error */
          close (*fd);
          free (fd);
        }
    }

  list_destroy (ip->sockets_to_close);

  free (ip->extra_arg);

  /* Any additional queued commands should be moved to add_to_pending
   * before destroy
   */
  assert (!ip->next);

  free (ip);
}

void
ipmipower_powercmd_setup ()
{
  assert (!pending);  /* need to cleanup first! */

  pending = list_create ((ListDelF)_destroy_ipmipower_powercmd);
  if (!pending)
    {
      IPMIPOWER_ERROR (("list_create: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }

  add_to_pending = list_create (NULL);
  if (!add_to_pending)
    {
      IPMIPOWER_ERROR (("list_create: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }
}

void
ipmipower_powercmd_cleanup ()
{
  assert (pending);  /* did not run ipmipower_powercmd_setup() */
  list_destroy (pending);
  list_destroy (add_to_pending);
  pending = NULL;
  add_to_pending = NULL;
}

void
ipmipower_powercmd_queue (ipmipower_power_cmd_t cmd,
                          struct ipmipower_connection *ic,
                          const char *extra_arg)
{
  ipmipower_powercmd_t ip;

  assert (pending);  /* did not run ipmipower_powercmd_setup() */
  assert (ic);
  assert (IPMIPOWER_POWER_CMD_VALID (cmd));

  ipmipower_connection_clear (ic);

  if (!(ip = (ipmipower_powercmd_t)malloc (sizeof (struct ipmipower_powercmd))))
    {
      IPMIPOWER_ERROR (("malloc: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }

  ip->cmd = cmd;
  ip->protocol_state = IPMIPOWER_PROTOCOL_STATE_START;

  /*
   * Protocol State Machine Variables
   */
#if 0
  /* Initialize when protocol really begins.  Necessary b/c of fanout support
   * For now just clear it.
   */
  if (gettimeofday (&(ip->time_begin), NULL) < 0)
    {
      IPMIPOWER_ERROR (("gettimeofday: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }
#else  /* 0 */
  memset (&(ip->time_begin), '\0', sizeof (struct timeval));
#endif  /* 0 */
  ip->retransmission_count = 0;
  ip->close_timeout = 0;

  /*
   * Protocol Maintenance Variables
   */

  ip->session_inbound_count = 0;

  if (cmd_args.common_args.driver_type == IPMI_DEVICE_LAN)
    {
      if (ipmi_check_session_sequence_number_1_5_init (&(ip->highest_received_sequence_number),
                                                       &(ip->previously_received_list)) < 0)
        {
          IPMIPOWER_ERROR (("ipmi_check_session_sequence_number_1_5_init: %s", strerror (errno)));
          exit (EXIT_FAILURE);
        }
    }
  else
    {
      if (ipmi_check_session_sequence_number_2_0_init (&(ip->highest_received_sequence_number),
                                                       &(ip->previously_received_list)) < 0)
        {
          IPMIPOWER_ERROR (("ipmi_check_session_sequence_number_2_0_init: %s", strerror (errno)));
          exit (EXIT_FAILURE);
        }
    }

  /* IPMI 1.5 */
#if 0
  if (cmd_args.common_args.driver_type == IPMI_DEVICE_LAN)
    {
      /* ip->permsgauth_enabled is set after the Get Authentication
       * Capabilities Response and/or Activate Session Response is
       * received
       */
      /* set to 0 below for time being */
    }
#else  /* 0 */
  ip->permsgauth_enabled = 0;
#endif /* 0 */

  /* IPMI 2.0 */

  if (cmd_args.common_args.driver_type == IPMI_DEVICE_LAN_2_0)
    {
      if (ipmi_cipher_suite_id_to_algorithms (cmd_args.common_args.cipher_suite_id,
                                              &(ip->authentication_algorithm),
                                              &(ip->integrity_algorithm),
                                              &(ip->confidentiality_algorithm)) < 0)
        {
          IPMIPOWER_ERROR (("ipmipower_powercmd_queue: ipmi_cipher_suite_id_to_algorithms: ",
                            "cmd_args.common_args.cipher_suite_id: %d: %s",
                            cmd_args.common_args.cipher_suite_id, strerror (errno)));
          exit (EXIT_FAILURE);
        }

      /*
       * IPMI Workaround (achu)
       *
       * Forgotten Motherboard
       *
       * Cipher suite IDs are attached to specific privilege levels
       * rather than a maximum privilege level limit.  So you can only
       * authenticate at the configured privilege level rather than a
       * privilege level <= to it.
       *
       * To deal with this situation.  We send the "request highest
       * privilege" flag in the open session request.  This should be
       * enough to work around this issue but still work with other
       * motherboards.
       */

      /* IPMI Workaround (achu)
       *
       * Discovered on SE7520AF2 with Intel Server Management Module
       * (Professional Edition)
       *
       * The Intel's return IPMI_PRIVILEGE_LEVEL_HIGHEST_LEVEL instead
       * of an actual privilege, so have to pass the actual privilege
       * we want to use.
       */

      /* IPMI Workaround (achu)
       *
       * Discovered on Sun Fire 4100, Inventec 5441/Dell Xanadu II,
       * Supermicro X8DTH, Supermicro X8DTG, Supermicro X8DTU, Intel
       * S5500WBV/Penguin Relion 700
       *
       * The remote BMC incorrectly calculates keys using the privilege
       * specified in the open session stage rather than the privilege
       * used during the RAKP1 stage.  This can be problematic if you
       * specify IPMI_PRIVILEGE_LEVEL_HIGHEST_LEVEL during that stage
       * instead of a real privilege level.  So we must pass the actual
       * privilege we want to use.
       */
      if (cmd_args.common_args.workaround_flags_outofband_2_0 & IPMI_PARSE_WORKAROUND_FLAGS_OUTOFBAND_2_0_INTEL_2_0_SESSION
          || cmd_args.common_args.workaround_flags_outofband_2_0 & IPMI_PARSE_WORKAROUND_FLAGS_OUTOFBAND_2_0_SUN_2_0_SESSION
          || cmd_args.common_args.workaround_flags_outofband_2_0 & IPMI_PARSE_WORKAROUND_FLAGS_OUTOFBAND_2_0_OPEN_SESSION_PRIVILEGE)
        ip->requested_maximum_privilege_level = cmd_args.common_args.privilege_level;
      else
        ip->requested_maximum_privilege_level = IPMI_PRIVILEGE_LEVEL_HIGHEST_LEVEL;
      memset (ip->sik_key, '\0', IPMI_MAX_SIK_KEY_LENGTH);
      ip->sik_key_ptr = ip->sik_key;
      ip->sik_key_len = IPMI_MAX_SIK_KEY_LENGTH;
      memset (ip->integrity_key, '\0', IPMI_MAX_INTEGRITY_KEY_LENGTH);
      ip->integrity_key_ptr = ip->integrity_key;
      ip->integrity_key_len = IPMI_MAX_INTEGRITY_KEY_LENGTH;
      memset (ip->confidentiality_key, '\0', IPMI_MAX_CONFIDENTIALITY_KEY_LENGTH);
      ip->confidentiality_key_ptr = ip->confidentiality_key;
      ip->confidentiality_key_len = IPMI_MAX_CONFIDENTIALITY_KEY_LENGTH;

      if (ipmi_get_random (&ip->initial_message_tag,
                           sizeof (ip->initial_message_tag)) < 0)
        {
          IPMIPOWER_ERROR (("ipmi_get_random: %s", strerror (errno)));
          exit (EXIT_FAILURE);
        }

      ip->message_tag_count = 0;
      ip->session_sequence_number = 0;
      ip->name_only_lookup = IPMI_NAME_ONLY_LOOKUP;

      /* In IPMI 2.0, session_ids of 0 are special */
      do
        {
          if (ipmi_get_random (&ip->remote_console_session_id,
                               sizeof (ip->remote_console_session_id)) < 0)
            {
              IPMIPOWER_ERROR (("ipmi_get_random: %s", strerror (errno)));
              exit (EXIT_FAILURE);
            }
        } while (!ip->remote_console_session_id);

      if (ipmi_get_random (ip->remote_console_random_number,
                           IPMI_REMOTE_CONSOLE_RANDOM_NUMBER_LENGTH) < 0)
        {
          IPMIPOWER_ERROR (("ipmi_get_random: %s", strerror (errno)));
          exit (EXIT_FAILURE);
        }
    }

  ip->wait_until_on_state = 0;
  ip->wait_until_off_state = 0;

  ip->ic = ic;

  if (!(ip->obj_rmcp_hdr_rq = fiid_obj_create (tmpl_rmcp_hdr)))
    {
      IPMIPOWER_ERROR (("fiid_obj_create: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }
  if (!(ip->obj_rmcp_hdr_rs = fiid_obj_create (tmpl_rmcp_hdr)))
    {
      IPMIPOWER_ERROR (("fiid_obj_create: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }
  if (!(ip->obj_lan_session_hdr_rq = fiid_obj_create (tmpl_lan_session_hdr)))
    {
      IPMIPOWER_ERROR (("fiid_obj_create: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }
  if (!(ip->obj_lan_session_hdr_rs = fiid_obj_create (tmpl_lan_session_hdr)))
    {
      IPMIPOWER_ERROR (("fiid_obj_create: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }
  if (!(ip->obj_lan_msg_hdr_rq = fiid_obj_create (tmpl_lan_msg_hdr_rq)))
    {
      IPMIPOWER_ERROR (("fiid_obj_create: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }
  if (!(ip->obj_lan_msg_hdr_rs = fiid_obj_create (tmpl_lan_msg_hdr_rs)))
    {
      IPMIPOWER_ERROR (("fiid_obj_create: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }
  if (!(ip->obj_lan_msg_trlr_rs = fiid_obj_create (tmpl_lan_msg_trlr)))
    {
      IPMIPOWER_ERROR (("fiid_obj_create: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }
  if (!(ip->obj_rmcpplus_session_hdr_rq = fiid_obj_create (tmpl_rmcpplus_session_hdr)))
    {
      IPMIPOWER_ERROR (("fiid_obj_create: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }
  if (!(ip->obj_rmcpplus_session_hdr_rs = fiid_obj_create (tmpl_rmcpplus_session_hdr)))
    {
      IPMIPOWER_ERROR (("fiid_obj_create: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }
  if (!(ip->obj_rmcpplus_payload_rs = fiid_obj_create (tmpl_rmcpplus_payload)))
    {
      IPMIPOWER_ERROR (("fiid_obj_create: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }
  if (!(ip->obj_rmcpplus_session_trlr_rq = fiid_obj_create (tmpl_rmcpplus_session_trlr)))
    {
      IPMIPOWER_ERROR (("fiid_obj_create: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }
  if (!(ip->obj_rmcpplus_session_trlr_rs = fiid_obj_create (tmpl_rmcpplus_session_trlr)))
    {
      IPMIPOWER_ERROR (("fiid_obj_create: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }
  if (!(ip->obj_authentication_capabilities_rq = fiid_obj_create (tmpl_cmd_get_channel_authentication_capabilities_rq)))
    {
      IPMIPOWER_ERROR (("fiid_obj_create: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }
  if (!(ip->obj_authentication_capabilities_rs = fiid_obj_create (tmpl_cmd_get_channel_authentication_capabilities_rs)))
    {
      IPMIPOWER_ERROR (("fiid_obj_create: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }
  if (!(ip->obj_get_session_challenge_rq = fiid_obj_create (tmpl_cmd_get_session_challenge_rq)))
    {
      IPMIPOWER_ERROR (("fiid_obj_create: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }
  if (!(ip->obj_get_session_challenge_rs = fiid_obj_create (tmpl_cmd_get_session_challenge_rs)))
    {
      IPMIPOWER_ERROR (("fiid_obj_create: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }
  if (!(ip->obj_activate_session_rq = fiid_obj_create (tmpl_cmd_activate_session_rq)))
    {
      IPMIPOWER_ERROR (("fiid_obj_create: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }
  if (!(ip->obj_activate_session_rs = fiid_obj_create (tmpl_cmd_activate_session_rs)))
    {
      IPMIPOWER_ERROR (("fiid_obj_create: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }
  if (!(ip->obj_open_session_rq = fiid_obj_create (tmpl_rmcpplus_open_session_request)))
    {
      IPMIPOWER_ERROR (("fiid_obj_create: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }
  if (!(ip->obj_open_session_rs = fiid_obj_create (tmpl_rmcpplus_open_session_response)))
    {
      IPMIPOWER_ERROR (("fiid_obj_create: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }
  if (!(ip->obj_rakp_message_1_rq = fiid_obj_create (tmpl_rmcpplus_rakp_message_1)))
    {
      IPMIPOWER_ERROR (("fiid_obj_create: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }
  if (!(ip->obj_rakp_message_2_rs = fiid_obj_create (tmpl_rmcpplus_rakp_message_2)))
    {
      IPMIPOWER_ERROR (("fiid_obj_create: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }
  if (!(ip->obj_rakp_message_3_rq = fiid_obj_create (tmpl_rmcpplus_rakp_message_3)))
    {
      IPMIPOWER_ERROR (("fiid_obj_create: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }
  if (!(ip->obj_rakp_message_4_rs = fiid_obj_create (tmpl_rmcpplus_rakp_message_4)))
    {
      IPMIPOWER_ERROR (("fiid_obj_create: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }
  if (!(ip->obj_set_session_privilege_level_rq = fiid_obj_create (tmpl_cmd_set_session_privilege_level_rq)))
    {
      IPMIPOWER_ERROR (("fiid_obj_create: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }
  if (!(ip->obj_set_session_privilege_level_rs = fiid_obj_create (tmpl_cmd_set_session_privilege_level_rs)))
    {
      IPMIPOWER_ERROR (("fiid_obj_create: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }
  if (!(ip->obj_get_chassis_status_rq = fiid_obj_create (tmpl_cmd_get_chassis_status_rq)))
    {
      IPMIPOWER_ERROR (("fiid_obj_create: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }
  if (!(ip->obj_get_chassis_status_rs = fiid_obj_create (tmpl_cmd_get_chassis_status_rs)))
    {
      IPMIPOWER_ERROR (("fiid_obj_create: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }
  if (!(ip->obj_chassis_control_rq = fiid_obj_create (tmpl_cmd_chassis_control_rq)))
    {
      IPMIPOWER_ERROR (("fiid_obj_create: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }
  if (!(ip->obj_chassis_control_rs = fiid_obj_create (tmpl_cmd_chassis_control_rs)))
    {
      IPMIPOWER_ERROR (("fiid_obj_create: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }
  if (!(ip->obj_chassis_identify_rq = fiid_obj_create (tmpl_cmd_chassis_identify_rq)))
    {
      IPMIPOWER_ERROR (("fiid_obj_create: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }
  if (!(ip->obj_chassis_identify_rs = fiid_obj_create (tmpl_cmd_chassis_identify_rs)))
    {
      IPMIPOWER_ERROR (("fiid_obj_create: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }

  if (cmd_args.oem_power_type != IPMIPOWER_OEM_POWER_TYPE_NONE)
    {
      if (cmd_args.oem_power_type == IPMIPOWER_OEM_POWER_TYPE_C410X)
        {
          if (!(ip->obj_c410x_get_sensor_reading_rq = fiid_obj_create (tmpl_cmd_get_sensor_reading_rq)))
            {
              IPMIPOWER_ERROR (("fiid_obj_create: %s", strerror (errno)));
              exit (EXIT_FAILURE);
            }
          if (!(ip->obj_c410x_get_sensor_reading_rs = fiid_obj_create (tmpl_cmd_get_sensor_reading_rs)))
            {
              IPMIPOWER_ERROR (("fiid_obj_create: %s", strerror (errno)));
              exit (EXIT_FAILURE);
            }
          if (!(ip->obj_c410x_slot_power_control_rq = fiid_obj_create (tmpl_cmd_c410x_slot_power_control_rq)))
            {
              IPMIPOWER_ERROR (("fiid_obj_create: %s", strerror (errno)));
              exit (EXIT_FAILURE);
            }
          if (!(ip->obj_c410x_slot_power_control_rs = fiid_obj_create (tmpl_cmd_c410x_slot_power_control_rs)))
            {
              IPMIPOWER_ERROR (("fiid_obj_create: %s", strerror (errno)));
              exit (EXIT_FAILURE);
            }
        }
    }
  else
    {
      ip->obj_c410x_get_sensor_reading_rq = NULL;
      ip->obj_c410x_get_sensor_reading_rs = NULL;
      ip->obj_c410x_slot_power_control_rq = NULL;
      ip->obj_c410x_slot_power_control_rs = NULL;
    }

  if (!(ip->obj_close_session_rq = fiid_obj_create (tmpl_cmd_close_session_rq)))
    {
      IPMIPOWER_ERROR (("fiid_obj_create: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }
  if (!(ip->obj_close_session_rs = fiid_obj_create (tmpl_cmd_close_session_rs)))
    {
      IPMIPOWER_ERROR (("fiid_obj_create: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }

  if (!(ip->sockets_to_close = list_create (NULL)))
    {
      IPMIPOWER_ERROR (("list_create: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }

  if (cmd_args.oem_power_type != IPMIPOWER_OEM_POWER_TYPE_NONE)
    {
      assert (ipmipower_oem_power_cmd_check_support_and_privilege (cmd, NULL, 0) > 0);
      assert (ipmipower_oem_power_cmd_check_extra_arg (extra_arg, NULL, 0) > 0);

      if (extra_arg)
        {
          if (!(ip->extra_arg = strdup (extra_arg)))
            {
              IPMIPOWER_ERROR (("strdup"));
              exit (EXIT_FAILURE);
            }
        }
      else
        ip->extra_arg = NULL;
    }
  else
    ip->extra_arg = NULL;

  /* When doing OEM power control, it is possible the user may specify
   * the same host multiple times.  This wouldn't be possible under
   * normal cases.  For example, under normal circumstances if the user did
   *
   * -h foohost,foohost --on
   *
   * foohost would be collapsed to just one "foohost" (through a call
   * to hostlist_uniq()), because it doesn't make sense to turn it on
   * twice.
   *
   * However, now someone might want to do
   *
   * --oem-power-type=FOO -h foohost+1,foohost+2 --on
   *
   * Which logically can make sense now.
   *
   * We do not want to do power control to the host in parallel b/c
   * many BMCs can't handle parallel sessions (you will BUSY errors).
   * So we will serialize power control operations to the same host.
   */

  /* XXX: The constant strcmp and searching of this list can be slow
   * (O(n^2)), but for the time being it is assumed this will not
   * be an overall performance issue for ipmipower.  If it does become
   * an issue, a bigger rearchitecture will be required.
   */

  ip->next = NULL;

  if (cmd_args.oem_power_type == IPMIPOWER_OEM_POWER_TYPE_C410X)
    {
      ipmipower_powercmd_t iptmp;

      if ((iptmp = list_find_first (pending,
                                    _find_ipmipower_powercmd,
                                    ip->ic->hostname)))
        {
          /* find the last one in the list */
          while (iptmp->next)
            iptmp = iptmp->next;
          iptmp->next = ip;
          return;
        }
    }

  if (!list_append (pending, ip))
    {
      IPMIPOWER_ERROR (("list_append: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }
}

int
ipmipower_powercmd_pending ()
{
  assert (pending);  /* did not run ipmipower_powercmd_setup() */

  return (!list_is_empty (pending));
}

/* _send_packet
 * - Send a packet of the specified type
 * - updates state and counts
 * - if this is a retransmission, do not update inbound and rqseq
 *   count.  BMC may need to know if this is a retransmission.  Must
 *   increment outbound sequence number, since BMC may increase outbound
 *   sequence number.
 */
static void
_send_packet (ipmipower_powercmd_t ip, ipmipower_packet_type_t pkt)
{
  uint8_t buf[IPMIPOWER_PACKET_BUFLEN];
  int ret, len = 0, dropped = 0;

  assert (ip);
  assert (IPMIPOWER_PACKET_TYPE_RQ (pkt));

  /* The following sequence number counts must be set before
   * ipmipower_packet_create, so the same value that is sent can be
   * matched later.
   */
  ip->ic->ipmi_requester_sequence_number_counter++;

  if (IPMIPOWER_PACKET_TYPE_IPMI_2_0_SETUP_RQ (pkt))
    ip->message_tag_count++;
  else if (cmd_args.common_args.driver_type == IPMI_DEVICE_LAN_2_0
           && IPMIPOWER_PACKET_TYPE_IPMI_SESSION_PACKET_RQ (pkt))
    {
      /* IPMI 2.0 is special, sequence numbers of 0 don't count */
      ip->session_sequence_number++;
      if (!ip->session_sequence_number)
        ip->session_sequence_number++;
    }

  len = ipmipower_packet_create (ip, pkt, buf, IPMIPOWER_PACKET_BUFLEN);
  ipmipower_packet_dump (ip, pkt, buf, len);

  if ((ret = cbuf_write (ip->ic->ipmi_out, buf, len, &dropped)) < 0)
    {
      IPMIPOWER_ERROR (("cbuf_write: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }

  if (ret != len)
    {
      IPMIPOWER_ERROR (("cbuf_write: incorrect bytes written %d", ret));
      exit (EXIT_FAILURE);
    }

  if (dropped)
    IPMIPOWER_DEBUG (("cbuf_write: dropped %d bytes", dropped));

  if (cmd_args.common_args.driver_type == IPMI_DEVICE_LAN
      && cmd_args.common_args.authentication_type == IPMI_AUTHENTICATION_TYPE_STRAIGHT_PASSWORD_KEY)
    secure_memset (buf, '\0', IPMIPOWER_PACKET_BUFLEN);

  switch (pkt)
    {
    case IPMIPOWER_PACKET_TYPE_AUTHENTICATION_CAPABILITIES_RQ:
      ip->protocol_state = IPMIPOWER_PROTOCOL_STATE_AUTHENTICATION_CAPABILITIES_SENT;
      break;
    case IPMIPOWER_PACKET_TYPE_GET_SESSION_CHALLENGE_RQ:
      ip->protocol_state = IPMIPOWER_PROTOCOL_STATE_GET_SESSION_CHALLENGE_SENT;
      break;
    case IPMIPOWER_PACKET_TYPE_ACTIVATE_SESSION_RQ:
      ip->protocol_state = IPMIPOWER_PROTOCOL_STATE_ACTIVATE_SESSION_SENT;

      /* IPMI Workaround (achu)
       *
       * Close all sockets that were saved during the Get Session
       * Challenge phase of the IPMI protocol.  See comments in
       * _retry_packets().
       */
      if (list_count (ip->sockets_to_close) > 0)
        {
          int *fd;
          while ((fd = list_pop (ip->sockets_to_close)))
            {
              /* cleanup path, ignore potential error */
              close (*fd);
              free (fd);
            }
        }
      break;
    case IPMIPOWER_PACKET_TYPE_OPEN_SESSION_REQUEST:
      ip->protocol_state = IPMIPOWER_PROTOCOL_STATE_OPEN_SESSION_REQUEST_SENT;
      break;
    case IPMIPOWER_PACKET_TYPE_RAKP_MESSAGE_1:
      ip->protocol_state = IPMIPOWER_PROTOCOL_STATE_RAKP_MESSAGE_1_SENT;
      break;
    case IPMIPOWER_PACKET_TYPE_RAKP_MESSAGE_3:
      ip->protocol_state = IPMIPOWER_PROTOCOL_STATE_RAKP_MESSAGE_3_SENT;
      break;
    case IPMIPOWER_PACKET_TYPE_SET_SESSION_PRIVILEGE_LEVEL_RQ:
      ip->protocol_state = IPMIPOWER_PROTOCOL_STATE_SET_SESSION_PRIVILEGE_LEVEL_SENT;
      break;
    case IPMIPOWER_PACKET_TYPE_GET_CHASSIS_STATUS_RQ:
      ip->protocol_state = IPMIPOWER_PROTOCOL_STATE_GET_CHASSIS_STATUS_SENT;
      break;
    case IPMIPOWER_PACKET_TYPE_CHASSIS_CONTROL_RQ:
      ip->protocol_state = IPMIPOWER_PROTOCOL_STATE_CHASSIS_CONTROL_SENT;
      break;
    case IPMIPOWER_PACKET_TYPE_CHASSIS_IDENTIFY_RQ:
      ip->protocol_state = IPMIPOWER_PROTOCOL_STATE_CHASSIS_IDENTIFY_SENT;
      break;
    case IPMIPOWER_PACKET_TYPE_C410X_GET_SENSOR_READING_RQ:
      ip->protocol_state = IPMIPOWER_PROTOCOL_STATE_C410X_GET_SENSOR_READING_SENT;
      break;
    case IPMIPOWER_PACKET_TYPE_C410X_SLOT_POWER_CONTROL_RQ:
      ip->protocol_state = IPMIPOWER_PROTOCOL_STATE_C410X_SLOT_POWER_CONTROL_SENT;
      break;
    case IPMIPOWER_PACKET_TYPE_CLOSE_SESSION_RQ:
      ip->protocol_state = IPMIPOWER_PROTOCOL_STATE_CLOSE_SESSION_SENT;
      break;
    default:
      IPMIPOWER_ERROR (("_send_packet: invalid pkt type: %d", pkt));
      exit (EXIT_FAILURE);
    }

  /* Session inbound count is incremented after the packet is sent,
   * since the first inbound sequence number is specified by the
   * activate session command.
   */
  if (cmd_args.common_args.driver_type == IPMI_DEVICE_LAN
      && IPMIPOWER_PACKET_TYPE_IPMI_SESSION_PACKET_RQ (pkt))
    ip->session_inbound_count++;

  if (gettimeofday (&(ip->ic->last_ipmi_send), NULL) < 0)
    {
      IPMIPOWER_ERROR (("gettimeofday: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }
}

/* _recv_packet
 * - Receive a packet
 * Returns 1 if packet is of correct size and passes checks
 * Returns 0 if no packet received yet or packet should be ignored
 * Returns -1 if packet returned error
 */
static int
_recv_packet (ipmipower_powercmd_t ip, ipmipower_packet_type_t pkt)
{
  uint8_t recv_buf[IPMIPOWER_PACKET_BUFLEN];
  int recv_len = 0;
  int rv = -1;
  uint64_t val;

  assert (ip);
  assert (IPMIPOWER_PACKET_TYPE_RS (pkt));

  if (!(recv_len = ipmipower_cbuf_peek_and_drop (ip->ic->ipmi_in,
                                                 recv_buf,
                                                 IPMIPOWER_PACKET_BUFLEN)))
    return (0);

  ipmipower_packet_dump (ip, pkt, recv_buf, recv_len);

  /* rv = 0 if the packet is unparseable */
  if (!ipmipower_packet_store (ip, pkt, recv_buf, recv_len))
    {
      rv = 0;
      goto cleanup;
    }

  if (pkt == IPMIPOWER_PACKET_TYPE_AUTHENTICATION_CAPABILITIES_RS
      || pkt == IPMIPOWER_PACKET_TYPE_GET_SESSION_CHALLENGE_RS)
    {
      if (!ipmipower_check_checksum (ip, pkt))
        {
          rv = 0;
          goto cleanup;
        }

      if (!ipmipower_check_network_function (ip, pkt))
        {
          rv = 0;
          goto cleanup;
        }

      if (!ipmipower_check_command (ip, pkt))
        {
          rv = 0;
          goto cleanup;
        }

      if (!ipmipower_check_requester_sequence_number (ip, pkt))
        {
          rv = 0;
          goto cleanup;
        }

      /* If everything else is correct besides completion code, packet
       * returned an error.
       */
      if (!ipmipower_check_completion_code (ip, pkt))
        {
          ipmipower_output (ipmipower_packet_errmsg (ip, pkt), ip->ic->hostname, ip->extra_arg);
          ip->retransmission_count = 0;  /* important to reset */
          if (gettimeofday (&ip->ic->last_ipmi_recv, NULL) < 0)
            {
              IPMIPOWER_ERROR (("gettimeofday: %s", strerror (errno)));
              exit (EXIT_FAILURE);
            }
          goto cleanup;
        }

      /* If packet is no good though, ignore it, treat it like a
       * checksum error.  Note, you must check after checking
       * completion code.
       */
      if (!ipmipower_check_packet (ip, pkt))
        {
          rv = 0;
          goto cleanup;
        }
    }
  else if (pkt == IPMIPOWER_PACKET_TYPE_ACTIVATE_SESSION_RS)
    {
      if (!ipmipower_check_checksum (ip, pkt))
        {
          rv = 0;
          goto cleanup;
        }

      if (!ipmipower_check_authentication_code (ip,
                                                pkt,
                                                recv_buf,
                                                recv_len))
        {
          rv = 0;
          goto cleanup;
        }

      if (!ipmipower_check_network_function (ip, pkt))
        {
          rv = 0;
          goto cleanup;
        }

      if (!ipmipower_check_command (ip, pkt))
        {
          rv = 0;
          goto cleanup;
        }

      if (!ipmipower_check_requester_sequence_number (ip, pkt))
        {
          rv = 0;
          goto cleanup;
        }

      /* If everything else is correct besides completion code, packet
       * returned an error.
       */
      if (!ipmipower_check_completion_code (ip, pkt))
        {
          ipmipower_output (ipmipower_packet_errmsg (ip, pkt), ip->ic->hostname, ip->extra_arg);
          ip->retransmission_count = 0;  /* important to reset */
          if (gettimeofday (&ip->ic->last_ipmi_recv, NULL) < 0)
            {
              IPMIPOWER_ERROR (("gettimeofday: %s", strerror (errno)));
              exit (EXIT_FAILURE);
            }
          goto cleanup;
        }

      /* If packet is no good though, ignore it, treat it like a
       * checksum error.  Note, you must check after checking
       * completion code.
       */
      if (!ipmipower_check_packet (ip, pkt))
        {
          rv = 0;
          goto cleanup;
        }

      /* achu:
       *
       * this should really be done in _process_ipmi_packets(), but
       * because we are going to clear out the lan session header (b/c
       * it has sensitive information in it), we'll do this here.
       */

      if (FIID_OBJ_GET (ip->obj_lan_session_hdr_rs,
                        "session_sequence_number",
                        &val) < 0)
        {
          IPMIPOWER_ERROR (("FIID_OBJ_GET: 'session_sequence_number': %s",
                            fiid_obj_errormsg (ip->obj_lan_session_hdr_rs)));
          exit (EXIT_FAILURE);
        }

      ip->highest_received_sequence_number = val;

      /* IPMI Workaround (achu)
       *
       * Discovered on Sun Fire 4100.
       *
       * The session sequence numbers for IPMI 1.5 are the wrong endian.
       * So we have to flip the bits to workaround it.
       */
      if (cmd_args.common_args.workaround_flags_outofband & IPMI_PARSE_WORKAROUND_FLAGS_OUTOFBAND_BIG_ENDIAN_SEQUENCE_NUMBER)
        {
          uint32_t tmp_session_sequence_number = ip->highest_received_sequence_number;

          ip->highest_received_sequence_number =
            ((tmp_session_sequence_number & 0xFF000000) >> 24)
            | ((tmp_session_sequence_number & 0x00FF0000) >> 8)
            | ((tmp_session_sequence_number & 0x0000FF00) << 8)
            | ((tmp_session_sequence_number & 0x000000FF) << 24);
        }
    }
  else if (cmd_args.common_args.driver_type == IPMI_DEVICE_LAN
           && IPMIPOWER_PACKET_TYPE_IPMI_SESSION_PACKET_RS (pkt))
    {
      if (!ipmipower_check_checksum (ip, pkt))
        {
          rv = 0;
          goto cleanup;
        }

      if (!ipmipower_check_authentication_code (ip,
                                                pkt,
                                                recv_buf,
                                                recv_len))
        {
          rv = 0;
          goto cleanup;
        }

      if (!ipmipower_check_outbound_sequence_number (ip, pkt))
        {
          rv = 0;
          goto cleanup;
        }

      if (!ipmipower_check_session_id (ip, pkt))
        {
          rv = 0;
          goto cleanup;
        }

      if (!ipmipower_check_network_function (ip, pkt))
        {
          rv = 0;
          goto cleanup;
        }

      if (!ipmipower_check_command (ip, pkt))
        {
          rv = 0;
          goto cleanup;
        }

      if (!ipmipower_check_requester_sequence_number (ip, pkt))
        {
          if (pkt == IPMIPOWER_PACKET_TYPE_CLOSE_SESSION_RS)
            goto close_session_workaround;
          rv = 0;
          goto cleanup;
        }

      /* If everything else is correct besides completion code, packet
       * returned an error.
       */
      if (!ipmipower_check_completion_code (ip, pkt))
        {
          if (pkt == IPMIPOWER_PACKET_TYPE_CLOSE_SESSION_RS)
            goto close_session_workaround;

          ipmipower_output (ipmipower_packet_errmsg (ip, pkt), ip->ic->hostname, ip->extra_arg);

          ip->retransmission_count = 0;  /* important to reset */
          if (gettimeofday (&ip->ic->last_ipmi_recv, NULL) < 0)
            {
              IPMIPOWER_ERROR (("gettimeofday: %s", strerror (errno)));
              exit (EXIT_FAILURE);
            }
          goto cleanup;
        }

      /* If packet is no good though, ignore it, treat it like a
       * checksum error.  Note, you must check after checking
       * completion code.
       */
      if (!ipmipower_check_packet (ip, pkt))
        {
          rv = 0;
          goto cleanup;
        }
    }
  else if (IPMIPOWER_PACKET_TYPE_IPMI_2_0_SETUP_RS (pkt))
    {
      if (!ipmipower_check_payload_type (ip, pkt))
        {
          rv = 0;
          goto cleanup;
        }

      if (!ipmipower_check_message_tag (ip, pkt))
        {
          rv = 0;
          goto cleanup;
        }

      /* I don't think there is a guarantee the data (authentication
       * keys, session id's, etc.) in the RAKP response will be valid
       * if there is a status code error.  So we check this status
       * code first, then the other stuff afterwards.
       */
      if (!ipmipower_check_rmcpplus_status_code (ip, pkt))
        {
          ipmipower_output (ipmipower_packet_errmsg (ip, pkt), ip->ic->hostname, ip->extra_arg);
          ip->retransmission_count = 0;  /* important to reset */
          if (gettimeofday (&ip->ic->last_ipmi_recv, NULL) < 0)
            {
              IPMIPOWER_ERROR (("gettimeofday: %s", strerror (errno)));
              exit (EXIT_FAILURE);
            }
          goto cleanup;
        }

      if (!ipmipower_check_session_id (ip, pkt))
        {
          rv = 0;
          goto cleanup;
        }

      if (pkt == IPMIPOWER_PACKET_TYPE_OPEN_SESSION_RESPONSE)
        {
          if (!ipmipower_check_open_session_response_privilege (ip, pkt))
            {
              ipmipower_output (IPMIPOWER_MSG_TYPE_PRIVILEGE_LEVEL_CANNOT_BE_OBTAINED, ip->ic->hostname, ip->extra_arg);
              goto cleanup;
            }
        }
      else if (pkt == IPMIPOWER_PACKET_TYPE_RAKP_MESSAGE_2)
        {
          if (!ipmipower_check_rakp_2_key_exchange_authentication_code (ip, pkt))
            {
              /* IPMI Compliance Issue
               *
               * On some systems, password could be correct, but
               * privilege is too high.  The error is b/c the
               * privilege error is not handled properly in the open
               * session stage (i.e. they tell me I can authenticate
               * at a high privilege level, that in reality is not
               * allowed).  Dunno how to deal with this.
               */
              ipmipower_output (IPMIPOWER_MSG_TYPE_PASSWORD_INVALID, ip->ic->hostname, ip->extra_arg);
              goto cleanup;
            }
        }
      else if (pkt == IPMIPOWER_PACKET_TYPE_RAKP_MESSAGE_4)
        {
          if (!ipmipower_check_rakp_4_integrity_check_value (ip, pkt))
            {
              ipmipower_output (IPMIPOWER_MSG_TYPE_K_G_INVALID, ip->ic->hostname, ip->extra_arg);
              goto cleanup;
            }
        }

      /* If packet is no good though, ignore it, treat it like a
       * checksum error.  Note, you must check after checking
       * completion code.
       */
      if (!ipmipower_check_packet (ip, pkt))
        {
          rv = 0;
          goto cleanup;
        }
    }
  else if (cmd_args.common_args.driver_type == IPMI_DEVICE_LAN_2_0
           && IPMIPOWER_PACKET_TYPE_IPMI_SESSION_PACKET_RS (pkt))
    {
      if (!ipmipower_check_payload_type (ip, pkt))
        {
          rv = 0;
          goto cleanup;
        }

      if (!ipmipower_check_payload_pad (ip, pkt))
        {
          rv = 0;
          goto cleanup;
        }

      if (!ipmipower_check_integrity_pad (ip, pkt))
        {
          rv = 0;
          goto cleanup;
        }

      if (!ipmipower_check_checksum (ip, pkt))
        {
          rv = 0;
          goto cleanup;
        }

      if (!ipmipower_check_authentication_code (ip,
                                                pkt,
                                                recv_buf,
                                                recv_len))
        {
          rv = 0;
          goto cleanup;
        }

      if (!ipmipower_check_outbound_sequence_number (ip, pkt))
        {
          rv = 0;
          goto cleanup;
        }

      if (!ipmipower_check_session_id (ip, pkt))
        {
          rv = 0;
          goto cleanup;
        }

      if (!ipmipower_check_network_function (ip, pkt))
        {
          rv = 0;
          goto cleanup;
        }

      if (!ipmipower_check_command (ip, pkt))
        {
          rv = 0;
          goto cleanup;
        }

      if (!ipmipower_check_requester_sequence_number (ip, pkt))
        {
          if (pkt == IPMIPOWER_PACKET_TYPE_CLOSE_SESSION_RS)
            goto close_session_workaround;
          rv = 0;
          goto cleanup;
        }

      /* If everything else is correct besides completion code, packet
       * returned an error.
       */
      if (!ipmipower_check_completion_code (ip, pkt))
        {
          if (pkt == IPMIPOWER_PACKET_TYPE_CLOSE_SESSION_RS)
            goto close_session_workaround;

          ip->retransmission_count = 0;  /* important to reset */
          if (gettimeofday (&ip->ic->last_ipmi_recv, NULL) < 0)
            {
              IPMIPOWER_ERROR (("gettimeofday: %s", strerror (errno)));
              exit (EXIT_FAILURE);
            }
          goto cleanup;
        }

      /* If packet is no good though, ignore it, treat it like a
       * checksum error.  Note, you must check after checking
       * completion code.
       */
      if (!ipmipower_check_packet (ip, pkt))
        {
          rv = 0;
          goto cleanup;
        }
    }

  /* Yipee everything passed, the packet is good.  Continue */

  /* achu: If this is the close session response and the packet is
   * mostly legit, go ahead and just accept the packet.  We'll
   * close the session anyways.
   */
 close_session_workaround:
  ip->retransmission_count = 0;  /* important to reset */
  if (gettimeofday (&ip->ic->last_ipmi_recv, NULL) < 0)
    {
      IPMIPOWER_ERROR (("gettimeofday: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }
  rv = 1;

 cleanup:
  /* Clear out data */
  if (cmd_args.common_args.driver_type == IPMI_DEVICE_LAN
      && cmd_args.common_args.authentication_type == IPMI_AUTHENTICATION_TYPE_STRAIGHT_PASSWORD_KEY)
    {
      secure_memset (recv_buf, '\0', IPMIPOWER_PACKET_BUFLEN);
      if (fiid_obj_clear (ip->obj_lan_session_hdr_rs) < 0)
        {
          IPMIPOWER_ERROR (("fiid_obj_clear: %s", fiid_obj_errormsg (ip->obj_lan_session_hdr_rs)));
          exit (EXIT_FAILURE);
        }
    }
  return (rv);
}

/* _has_timed_out
 * - Check if command timed out
 * Returns 1 if timed out, 0 if not
 */
static int
_has_timed_out (ipmipower_powercmd_t ip)
{
  struct timeval cur_time, result;
  unsigned int session_timeout;

  assert (ip);

  /* If we haven't started yet */
  if (ip->protocol_state == IPMIPOWER_PROTOCOL_STATE_START)
    return (0);

  if (gettimeofday (&cur_time, NULL) < 0)
    {
      IPMIPOWER_ERROR (("gettimeofday: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }

  timeval_sub (&cur_time, &(ip->time_begin), &result);
  timeval_millisecond_calc (&result, &session_timeout);

  /* Must use >=, otherwise we could potentially spin */
  if (session_timeout >= cmd_args.common_args.session_timeout)
    {
      /* Don't bother outputting timeout if we have finished the power
         control operation */
      if (ip->protocol_state != IPMIPOWER_PROTOCOL_STATE_CLOSE_SESSION_SENT)
        {
          /* Special cases */
          if (ip->protocol_state == IPMIPOWER_PROTOCOL_STATE_AUTHENTICATION_CAPABILITIES_SENT)
            ipmipower_output (IPMIPOWER_MSG_TYPE_CONNECTION_TIMEOUT, ip->ic->hostname, ip->extra_arg);
          else if (ip->protocol_state == IPMIPOWER_PROTOCOL_STATE_ACTIVATE_SESSION_SENT)
            ipmipower_output (IPMIPOWER_MSG_TYPE_PASSWORD_VERIFICATION_TIMEOUT, ip->ic->hostname, ip->extra_arg);
          else
            ipmipower_output (IPMIPOWER_MSG_TYPE_SESSION_TIMEOUT, ip->ic->hostname, ip->extra_arg);
        }
      return (1);
    }

  return (0);
}

/* _retry_packets
 * - Check if we should retransmit and retransmit if necessary
 * Returns 1 if we sent a packet, 0 if not
 */
static int
_retry_packets (ipmipower_powercmd_t ip)
{
  struct timeval cur_time, end_time, result;
  unsigned int time_since_last_ipmi_send;
  unsigned int time_left;
  unsigned int retransmission_timeout;

  assert (ip);

  /* Don't retransmit if any of the following are true */
  if (ip->protocol_state == IPMIPOWER_PROTOCOL_STATE_START) /* we haven't started yet */
    return (0);

  /* Did we timeout on this packet? */
  if ((ip->wait_until_on_state
       && ip->cmd == IPMIPOWER_POWER_CMD_POWER_ON)
      || (ip->wait_until_off_state
          && ip->cmd == IPMIPOWER_POWER_CMD_POWER_OFF))
    retransmission_timeout = cmd_args.retransmission_wait_timeout * (1 + (ip->retransmission_count/cmd_args.retransmission_backoff_count));
  else
    retransmission_timeout = cmd_args.common_args.retransmission_timeout * (1 + (ip->retransmission_count/cmd_args.retransmission_backoff_count));

  if (gettimeofday (&cur_time, NULL) < 0)
    {
      IPMIPOWER_ERROR (("gettimeofday: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }

  timeval_sub (&cur_time, &(ip->ic->last_ipmi_send), &result);
  timeval_millisecond_calc (&result, &time_since_last_ipmi_send);

  if (time_since_last_ipmi_send < retransmission_timeout)
    return (0);

  /* Do we have enough time to retransmit? */
  timeval_add_ms (&cur_time, cmd_args.common_args.session_timeout, &end_time);
  timeval_sub (&end_time, &cur_time, &result);
  timeval_millisecond_calc (&result, &time_left);
  if (time_left < retransmission_timeout)
    return (0);

  ip->retransmission_count++;

  IPMIPOWER_DEBUG (("host = %s; p = %d; Sending retry, retry count=%d",
                    ip->ic->hostname,
                    ip->protocol_state,
                    ip->retransmission_count));

  switch (ip->protocol_state)
    {
    case IPMIPOWER_PROTOCOL_STATE_AUTHENTICATION_CAPABILITIES_SENT:
      _send_packet (ip, IPMIPOWER_PACKET_TYPE_AUTHENTICATION_CAPABILITIES_RQ);
      break;
    case IPMIPOWER_PROTOCOL_STATE_GET_SESSION_CHALLENGE_SENT:
      {
        /* IPMI Workaround (achu)
         *
         * Discovered on Intel Tiger4 (SR870BN4)
         *
         * If the reply from a previous Get Session Challenge request is
         * lost on the network, the following retransmission will make
         * the BMC confused and it will not respond to future packets.
         *
         * The problem seems to exist only when the retransmitted packet
         * is transmitted from the same source port.  Therefore, the fix
         * is to send the retransmission from a different source port.
         * So we'll create a new socket, re-bind to an ephemereal port
         * (guaranteeing us a brand new port), and store this new
         * socket.
         *
         * In the event we need to resend this packet multiple times, we
         * do not want the chance that old ports will be used again.  We
         * store the old file descriptrs (which are bound to the old
         * ports) on a list, and close all of them after we have gotten
         * past the Get Session Challenge phase of the protocol.
         */
        int new_fd, *old_fd;

        if ((new_fd = socket (ip->ic->srcaddr->sa_family, SOCK_DGRAM, 0)) < 0)
          {
            if (errno != EMFILE)
              {
                IPMIPOWER_ERROR (("socket: %s", strerror (errno)));
                exit (EXIT_FAILURE);
              }

            ipmipower_output (IPMIPOWER_MSG_TYPE_RESOURCES, ip->ic->hostname, ip->extra_arg);
            return (-1);
          }

        if (bind (new_fd, ip->ic->srcaddr, ip->ic->srcaddrlen) < 0)
          {
            IPMIPOWER_ERROR (("bind: %s", strerror (errno)));
            exit (EXIT_FAILURE);
          }

        if (!(old_fd = (int *)malloc (sizeof (int))))
          {
            IPMIPOWER_ERROR (("malloc: %s", strerror (errno)));
            exit (EXIT_FAILURE);
          }

        *old_fd = ip->ic->ipmi_fd;
        list_push (ip->sockets_to_close, old_fd);

        ip->ic->ipmi_fd = new_fd;

        _send_packet (ip, IPMIPOWER_PACKET_TYPE_GET_SESSION_CHALLENGE_RQ);
      }
      break;
    case IPMIPOWER_PROTOCOL_STATE_ACTIVATE_SESSION_SENT:
      _send_packet (ip, IPMIPOWER_PACKET_TYPE_ACTIVATE_SESSION_RQ);
      break;
    case IPMIPOWER_PROTOCOL_STATE_OPEN_SESSION_REQUEST_SENT:
      _send_packet (ip, IPMIPOWER_PACKET_TYPE_OPEN_SESSION_REQUEST);
      break;
    case IPMIPOWER_PROTOCOL_STATE_RAKP_MESSAGE_1_SENT:
      _send_packet (ip, IPMIPOWER_PACKET_TYPE_RAKP_MESSAGE_1);
      break;
    case IPMIPOWER_PROTOCOL_STATE_RAKP_MESSAGE_3_SENT:
      _send_packet (ip, IPMIPOWER_PACKET_TYPE_RAKP_MESSAGE_3);
      break;
    case IPMIPOWER_PROTOCOL_STATE_SET_SESSION_PRIVILEGE_LEVEL_SENT:
      _send_packet (ip, IPMIPOWER_PACKET_TYPE_SET_SESSION_PRIVILEGE_LEVEL_RQ);
      break;
    case IPMIPOWER_PROTOCOL_STATE_GET_CHASSIS_STATUS_SENT:
      _send_packet (ip, IPMIPOWER_PACKET_TYPE_GET_CHASSIS_STATUS_RQ);
      break;
    case IPMIPOWER_PROTOCOL_STATE_CHASSIS_CONTROL_SENT:
      _send_packet (ip, IPMIPOWER_PACKET_TYPE_CHASSIS_CONTROL_RQ);
      break;
    case IPMIPOWER_PROTOCOL_STATE_CHASSIS_IDENTIFY_SENT:
      _send_packet (ip, IPMIPOWER_PACKET_TYPE_CHASSIS_IDENTIFY_RQ);
      break;
    case IPMIPOWER_PROTOCOL_STATE_C410X_GET_SENSOR_READING_SENT:
      _send_packet (ip, IPMIPOWER_PACKET_TYPE_C410X_GET_SENSOR_READING_RQ);
      break;
    case IPMIPOWER_PROTOCOL_STATE_C410X_SLOT_POWER_CONTROL_SENT:
      _send_packet (ip, IPMIPOWER_PACKET_TYPE_C410X_SLOT_POWER_CONTROL_RQ);
      break;
    case IPMIPOWER_PROTOCOL_STATE_CLOSE_SESSION_SENT:
      {
        /*
         * It's pointless to retransmit a close-session.
         *
         * 1) The power control operation has already completed.
         *
         * 2) There is no guarantee the remote BMC will respond.  If the
         * previous close session response was dropped by the network,
         * then the session has already been closed by the BMC.  Any
         * retransmission will send a session id that is unknown to the
         * BMC, and they will either respond with an error or ignore the
         * packet.
         *
         * _send_packet(ip, IPMIPOWER_PACKET_TYPE_CLOSE_SESSION_RQ);
         */
        ip->close_timeout++;
        return (0);
      }
      break;
    default:
      IPMIPOWER_ERROR (("_retry_packets: invalid protocol state: %d\n",
                        ip->protocol_state));
      exit (EXIT_FAILURE);
    }

  return (1);
}

/* _check_ipmi_1_5_authentication_capabilities
 *
 * Check the contents of a ipmi 1.5 or 2.0 authentication capabilities
 * response.
 *
 * Returns  0 if authentication passed and the protocol should continue
 * Returns -1 on ipmi protocol error or discovery error
 */
static int
_check_ipmi_1_5_authentication_capabilities (ipmipower_powercmd_t ip)
{
  uint8_t authentication_status_per_message_authentication;
  uint64_t val;
  int ret;

  assert (ip);

  if (FIID_OBJ_GET (ip->obj_authentication_capabilities_rs,
                    "authentication_status.per_message_authentication",
                    &val) < 0)
    {
      IPMIPOWER_ERROR (("FIID_OBJ_GET: 'authentication_status.per_message_authentication': %s",
                        fiid_obj_errormsg (ip->obj_authentication_capabilities_rs)));
      exit (EXIT_FAILURE);
    }
  authentication_status_per_message_authentication = val;

  /* IPMI Workaround (achu)
   *
   * Discovered on an ASUS P5M2 motherboard.
   *
   * The ASUS motherboard reports incorrect settings of anonymous
   * vs. null vs non-null username capabilities. The workaround is to
   * skip these checks.
   */
  if (!(cmd_args.common_args.workaround_flags_outofband & IPMI_PARSE_WORKAROUND_FLAGS_OUTOFBAND_AUTHENTICATION_CAPABILITIES))
    {
      if ((ret = ipmi_check_authentication_capabilities_username (cmd_args.common_args.username,
                                                                  cmd_args.common_args.password,
                                                                  ip->obj_authentication_capabilities_rs)) < 0)
        {
          IPMIPOWER_ERROR (("ipmi_check_authentication_capabilities_username: %s",
                            strerror (errno)));
          exit (EXIT_FAILURE);
        }

      if (!ret)
        {
          ipmipower_output (IPMIPOWER_MSG_TYPE_USERNAME_INVALID, ip->ic->hostname, ip->extra_arg);
          return (-1);
        }
    }

  /* IPMI Workaround (achu)
   *
   * Not discovered yet, assume some motherboard will have it some
   * day.
   *
   * Authentication capabilities flags are not listed properly in the
   * response.  The workaround is to skip these checks.
   */
  if (!(cmd_args.common_args.workaround_flags_outofband & IPMI_PARSE_WORKAROUND_FLAGS_OUTOFBAND_AUTHENTICATION_CAPABILITIES))
    {
      if ((ret = ipmi_check_authentication_capabilities_authentication_type (cmd_args.common_args.authentication_type,
                                                                             ip->obj_authentication_capabilities_rs)) < 0)
        {
          IPMIPOWER_ERROR (("ipmi_check_authentication_capabilities_authentication_type: %s",
                            strerror (errno)));
          exit (EXIT_FAILURE);
        }

      if (!ret)
        {
          ipmipower_output (IPMIPOWER_MSG_TYPE_AUTHENTICATION_TYPE_UNAVAILABLE, ip->ic->hostname, ip->extra_arg);
          return (-1);
        }
    }

  /* IPMI Workaround (achu)
   *
   * Discovered on IBM eServer 325
   *
   * The remote BMC ignores if permsg authentiction is enabled
   * or disabled.  So we need to force it no matter what.
   */
  if (!(cmd_args.common_args.workaround_flags_outofband & IPMI_PARSE_WORKAROUND_FLAGS_OUTOFBAND_FORCE_PERMSG_AUTHENTICATION))
    {
      if (!authentication_status_per_message_authentication)
        ip->permsgauth_enabled = 1;
      else
        ip->permsgauth_enabled = 0;
    }
  else
    ip->permsgauth_enabled = 1;

  return (0);
}

/* _check_ipmi_2_0_authentication_capabilities
 *
 * Check the contents of a ipmi 2.0 authentication capabilities response.
 *
 * Returns  0 if authentication passed
 * Returns -1 on ipmi protocol error
 */
static int
_check_ipmi_2_0_authentication_capabilities (ipmipower_powercmd_t ip)
{
  void *tmp_k_g_ptr = NULL;
  int ret;

  assert (ip);

  if ((ret = ipmi_check_authentication_capabilities_ipmi_2_0 (ip->obj_authentication_capabilities_rs)) < 0)
    {
      IPMIPOWER_ERROR (("ipmi_check_authentication_capabilities_ipmi_2_0: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }

  if (!ret)
    {
      ipmipower_output (IPMIPOWER_MSG_TYPE_IPMI_2_0_UNAVAILABLE, ip->ic->hostname, ip->extra_arg);
      return (-1);
    }

  /* IPMI Workaround (achu)
   *
   * Discovered on an ASUS P5M2 motherboard.
   *
   * The ASUS motherboard reports incorrect settings of anonymous
   * vs. null vs non-null username capabilities.  The workaround is to
   * skip these checks.
   *
   * Discovered on an ASUS P5MT-R motherboard
   *
   * K_g status is reported incorrectly too.  Again, skip the checks.
   */
  if (!(cmd_args.common_args.workaround_flags_outofband_2_0 & IPMI_PARSE_WORKAROUND_FLAGS_OUTOFBAND_2_0_AUTHENTICATION_CAPABILITIES))
    {
      if ((ret = ipmi_check_authentication_capabilities_username (cmd_args.common_args.username,
                                                                  cmd_args.common_args.password,
                                                                  ip->obj_authentication_capabilities_rs)) < 0)
        {
          IPMIPOWER_ERROR (("ipmi_check_authentication_capabilities_username: %s",
                            strerror (errno)));
          exit (EXIT_FAILURE);
        }

      if (!ret)
        {
          ipmipower_output (IPMIPOWER_MSG_TYPE_USERNAME_INVALID, ip->ic->hostname, ip->extra_arg);
          return (-1);
        }

      if (cmd_args.common_args.k_g_len)
        tmp_k_g_ptr = cmd_args.common_args.k_g;

      if ((ret = ipmi_check_authentication_capabilities_k_g (tmp_k_g_ptr,
                                                             ip->obj_authentication_capabilities_rs)) < 0)
        {
          IPMIPOWER_ERROR (("ipmi_check_authentication_capabilities_k_g: %s",
                            strerror (errno)));
          exit (EXIT_FAILURE);
        }

      if (!ret)
        {
          ipmipower_output (IPMIPOWER_MSG_TYPE_K_G_INVALID, ip->ic->hostname, ip->extra_arg);
          return (-1);
        }
    }

  return (0);
}

/* _check_activate_session_authentication_type
 *
 * Check if the activate session response has the appropriate
 * authentication type.
 *
 * Returns  0 if yes
 * Returns -1 if no, which is an ipmi protocol error
 */
static int
_check_activate_session_authentication_type (ipmipower_powercmd_t ip)
{
  uint8_t authentication_type;
  uint64_t val;

  assert (ip);
  assert (ip->protocol_state == IPMIPOWER_PROTOCOL_STATE_ACTIVATE_SESSION_SENT);

  if (FIID_OBJ_GET (ip->obj_activate_session_rs,
                    "authentication_type",
                    &val) < 0)
    {
      IPMIPOWER_ERROR (("FIID_OBJ_GET: 'authentication_type': %s",
                        fiid_obj_errormsg (ip->obj_activate_session_rs)));
      exit (EXIT_FAILURE);
    }
  authentication_type = val;

  if (cmd_args.common_args.workaround_flags_outofband & IPMI_PARSE_WORKAROUND_FLAGS_OUTOFBAND_FORCE_PERMSG_AUTHENTICATION)
    return (0);

  /* IPMI Workaround (achu)
   *
   * Discovered on Supermicro H8QME with SIMSO daughter card.
   *
   * (Note: This could work for "IBM eServer 325" per msg auth
   * problem.  But I don't have hardware to test it :-()
   *
   * The remote BMC ignores if permsg authentiction is disabled.
   * Handle it appropriately by just not doing permsg authentication.
   */
  if (!ip->permsgauth_enabled)
    {
      if (authentication_type != IPMI_AUTHENTICATION_TYPE_NONE)
        {
          IPMIPOWER_DEBUG (("host = %s; p = %d; not none authentcation",
                            ip->ic->hostname,
                            ip->protocol_state));

          ip->permsgauth_enabled = 1;
        }
    }

  if (ip->permsgauth_enabled)
    {
      if (authentication_type != cmd_args.common_args.authentication_type)
        {
          IPMIPOWER_DEBUG (("host = %s; p = %d; authentication_type mismatch",
                            ip->ic->hostname,
                            ip->protocol_state));

          ipmipower_output (IPMIPOWER_MSG_TYPE_BMC_ERROR, ip->ic->hostname, ip->extra_arg);

          ip->retransmission_count = 0;  /* important to reset */
          if (gettimeofday (&ip->ic->last_ipmi_recv, NULL) < 0)
            {
              IPMIPOWER_ERROR (("gettimeofday: %s", strerror (errno)));
              exit (EXIT_FAILURE);
            }
          return (-1);
        }
    }

  return (0);
}

/* _calculate_cipher_keys
 *
 * Calculate cipher keys for the remaining IPMI 2.0 protocol
 *
 * Returns  0 on success
 * Returns -1 on ipmi protocol error
 */
static int
_calculate_cipher_keys (ipmipower_powercmd_t ip)
{
  uint8_t managed_system_random_number[IPMI_MANAGED_SYSTEM_RANDOM_NUMBER_LENGTH];
  int managed_system_random_number_len;
  char *username;
  char username_buf[IPMI_MAX_USER_NAME_LENGTH+1];
  unsigned int username_len;
  char *password;
  unsigned int password_len;
  void *k_g;

  assert (ip);
  assert (ip->protocol_state == IPMIPOWER_PROTOCOL_STATE_RAKP_MESSAGE_1_SENT);

  /* IPMI Workaround (achu)
   *
   * Discovered on SE7520AF2 with Intel Server Management Module
   * (Professional Edition)
   *
   * The username must be padded despite explicitly not being
   * allowed.  "No Null characters (00h) are allowed in the name".
   * Table 13-11 in the IPMI 2.0 spec.
   */
  if (cmd_args.common_args.workaround_flags_outofband_2_0 & IPMI_PARSE_WORKAROUND_FLAGS_OUTOFBAND_2_0_INTEL_2_0_SESSION)
    {
      memset (username_buf, '\0', IPMI_MAX_USER_NAME_LENGTH+1);
      if (cmd_args.common_args.username)
        strcpy (username_buf, cmd_args.common_args.username);
      username = username_buf;
      username_len = IPMI_MAX_USER_NAME_LENGTH;
    }
  else
    {
      username = cmd_args.common_args.username;
      username_len = (username) ? strlen (username) : 0;
    }

  password = cmd_args.common_args.password;
  password_len = (password) ? strlen (password) : 0;

  /* IPMI Workaround (achu)
   *
   * Discovered on SE7520AF2 with Intel Server Management Module
   * (Professional Edition)
   *
   * When the authentication algorithm is HMAC-MD5-128 and the
   * password is greater than 16 bytes, the Intel BMC truncates the
   * password to 16 bytes when generating keys, hashes, etc.  So we
   * have to do the same when generating keys, hashes, etc.
   */
  if ((cmd_args.common_args.workaround_flags_outofband_2_0 & IPMI_PARSE_WORKAROUND_FLAGS_OUTOFBAND_2_0_INTEL_2_0_SESSION)
      && ip->authentication_algorithm == IPMI_AUTHENTICATION_ALGORITHM_RAKP_HMAC_MD5
      && password_len > IPMI_1_5_MAX_PASSWORD_LENGTH)
    password_len = IPMI_1_5_MAX_PASSWORD_LENGTH;

  if (cmd_args.common_args.k_g_len)
    k_g = cmd_args.common_args.k_g;
  else
    k_g = NULL;

  if ((managed_system_random_number_len = fiid_obj_get_data (ip->obj_rakp_message_2_rs,
                                                             "managed_system_random_number",
                                                             managed_system_random_number,
                                                             IPMI_MANAGED_SYSTEM_RANDOM_NUMBER_LENGTH)) < 0)
    {
      IPMIPOWER_ERROR (("fiid_obj_get_data: 'managed_system_random_number': %s",
                        fiid_obj_errormsg (ip->obj_rakp_message_2_rs)));
      exit (EXIT_FAILURE);
    }

  if (ipmi_calculate_rmcpplus_session_keys (ip->authentication_algorithm,
                                            ip->integrity_algorithm,
                                            ip->confidentiality_algorithm,
                                            password,
                                            password_len,
                                            k_g,
                                            (k_g) ? cmd_args.common_args.k_g_len : 0,
                                            ip->remote_console_random_number,
                                            IPMI_REMOTE_CONSOLE_RANDOM_NUMBER_LENGTH,
                                            managed_system_random_number,
                                            managed_system_random_number_len,
                                            ip->name_only_lookup,
                                            cmd_args.common_args.privilege_level,
                                            username,
                                            username_len,
                                            &(ip->sik_key_ptr),
                                            &(ip->sik_key_len),
                                            &(ip->integrity_key_ptr),
                                            &(ip->integrity_key_len),
                                            &(ip->confidentiality_key_ptr),
                                            &(ip->confidentiality_key_len)) < 0)
    {
      IPMIPOWER_ERROR (("_calculate_cipher_keys(%s:%d): ipmi_calculate_rmcpplus_session_keys: %s",
                        ip->ic->hostname, ip->protocol_state, strerror (errno)));
      exit (EXIT_FAILURE);
    }

  return (0);
}

/* _process_ipmi_packets
 * - Main function that handles packet sends/receives for
 *   the power control protocol
 * - Returns timeout length, or < 0 if command completed and should
 *   be removed from pending.
 */
static int
_process_ipmi_packets (ipmipower_powercmd_t ip)
{
  struct timeval cur_time, end_time, result;
  unsigned int timeout;
  uint64_t val;
  int rv;

  assert (ip);
  assert (IPMIPOWER_PROTOCOL_STATE_VALID (ip->protocol_state));
  assert (IPMIPOWER_OEM_POWER_TYPE_VALID (cmd_args.oem_power_type));

  /* if timeout, give up */
  if (_has_timed_out (ip))
    return (-1);

  /* retransmit? */
  if ((rv = _retry_packets (ip)))
    {
      if (rv < 0)
        return (-1);
      goto done;
    }

  if (ip->protocol_state == IPMIPOWER_PROTOCOL_STATE_START)
    {
      /* Don't execute if fanout turned on and we're in the middle of too
       * many power commands.
       */
      if (cmd_args.common_args.fanout
          && (executing_count >= cmd_args.common_args.fanout))
        return (cmd_args.common_args.session_timeout);

      _send_packet (ip, IPMIPOWER_PACKET_TYPE_AUTHENTICATION_CAPABILITIES_RQ);

      if (gettimeofday (&(ip->time_begin), NULL) < 0)
        {
          IPMIPOWER_ERROR (("gettimeofday: %s", strerror (errno)));
          exit (EXIT_FAILURE);
        }
      executing_count++;
    }
  else if (ip->protocol_state == IPMIPOWER_PROTOCOL_STATE_AUTHENTICATION_CAPABILITIES_SENT)
    {
      if ((rv = _recv_packet (ip, IPMIPOWER_PACKET_TYPE_AUTHENTICATION_CAPABILITIES_RS)) != 1)
        {
          if (rv < 0)
            return (-1);
          goto done;
        }

      if (cmd_args.common_args.driver_type == IPMI_DEVICE_LAN_2_0)
        {
          if (_check_ipmi_2_0_authentication_capabilities (ip) < 0)
            return (-1);

          _send_packet (ip, IPMIPOWER_PACKET_TYPE_OPEN_SESSION_REQUEST);
        }
      else
        {
          if (_check_ipmi_1_5_authentication_capabilities (ip) < 0)
            return (-1);

          _send_packet (ip, IPMIPOWER_PACKET_TYPE_GET_SESSION_CHALLENGE_RQ);
        }
    }
  else if (ip->protocol_state == IPMIPOWER_PROTOCOL_STATE_GET_SESSION_CHALLENGE_SENT)
    {
      if ((rv = _recv_packet (ip, IPMIPOWER_PACKET_TYPE_GET_SESSION_CHALLENGE_RS)) != 1)
        {
          if (rv < 0)
            /* XXX Session is not up, is it ok to quit here?  Or
             * should we timeout?? */
            return (-1);
          goto done;
        }

      _send_packet (ip, IPMIPOWER_PACKET_TYPE_ACTIVATE_SESSION_RQ);
    }
  else if (ip->protocol_state == IPMIPOWER_PROTOCOL_STATE_ACTIVATE_SESSION_SENT)
    {
      if ((rv = _recv_packet (ip, IPMIPOWER_PACKET_TYPE_ACTIVATE_SESSION_RS)) != 1)
        {
          if (rv < 0)
            /* XXX Session is not up, is it ok to quit here?  Or
             * should we timeout?? */
            return (-1);
          goto done;
        }

      if (_check_activate_session_authentication_type (ip) < 0)
        /* XXX Session is not up, is it ok to quit here?  Or
         * should we timeout?? */
        return (-1);

      _send_packet (ip, IPMIPOWER_PACKET_TYPE_SET_SESSION_PRIVILEGE_LEVEL_RQ);
    }
  else if (ip->protocol_state == IPMIPOWER_PROTOCOL_STATE_OPEN_SESSION_REQUEST_SENT)
    {
      if ((rv = _recv_packet (ip, IPMIPOWER_PACKET_TYPE_OPEN_SESSION_RESPONSE)) != 1)
        {
          if (rv < 0)
            /* XXX Session is not up, is it ok to quit here?  Or
             * should we timeout?? */
            return (-1);
          goto done;
        }

      _send_packet (ip, IPMIPOWER_PACKET_TYPE_RAKP_MESSAGE_1);
    }
  else if (ip->protocol_state == IPMIPOWER_PROTOCOL_STATE_RAKP_MESSAGE_1_SENT)
    {
      if ((rv = _recv_packet (ip, IPMIPOWER_PACKET_TYPE_RAKP_MESSAGE_2)) != 1)
        {
          if (rv < 0)
            /* XXX Session is not up, is it ok to quit here?  Or
             * should we timeout?? */
            return (-1);
          goto done;
        }

      if (_calculate_cipher_keys (ip) < 0)
        return (-1);

      _send_packet (ip, IPMIPOWER_PACKET_TYPE_RAKP_MESSAGE_3);
    }
  else if (ip->protocol_state == IPMIPOWER_PROTOCOL_STATE_RAKP_MESSAGE_3_SENT)
    {
      if ((rv = _recv_packet (ip, IPMIPOWER_PACKET_TYPE_RAKP_MESSAGE_4)) != 1)
        {
          if (rv < 0)
            /* XXX Session is not up, is it ok to quit here?  Or
             * should we timeout?? */
            return (-1);
          goto done;
        }

      _send_packet (ip, IPMIPOWER_PACKET_TYPE_SET_SESSION_PRIVILEGE_LEVEL_RQ);
    }
  else if (ip->protocol_state == IPMIPOWER_PROTOCOL_STATE_SET_SESSION_PRIVILEGE_LEVEL_SENT)
    {
      if ((rv = _recv_packet (ip, IPMIPOWER_PACKET_TYPE_SET_SESSION_PRIVILEGE_LEVEL_RS)) != 1)
        {
          if (rv < 0)
            /* Session is up, so close it */
            _send_packet (ip, IPMIPOWER_PACKET_TYPE_CLOSE_SESSION_RQ);
          goto done;
        }

      if (cmd_args.oem_power_type == IPMIPOWER_OEM_POWER_TYPE_NONE)
        {
          if (ip->cmd == IPMIPOWER_POWER_CMD_POWER_STATUS
              || ip->cmd == IPMIPOWER_POWER_CMD_IDENTIFY_STATUS
              || (cmd_args.on_if_off
                  && (ip->cmd == IPMIPOWER_POWER_CMD_POWER_CYCLE
                      || ip->cmd == IPMIPOWER_POWER_CMD_POWER_RESET)))
            _send_packet (ip, IPMIPOWER_PACKET_TYPE_GET_CHASSIS_STATUS_RQ);
          else if (ip->cmd == IPMIPOWER_POWER_CMD_IDENTIFY_ON
                   || ip->cmd == IPMIPOWER_POWER_CMD_IDENTIFY_OFF)
            _send_packet (ip, IPMIPOWER_PACKET_TYPE_CHASSIS_IDENTIFY_RQ);
          else /* on, off, cycle, reset, pulse diag interupt, soft shutdown */
            _send_packet (ip, IPMIPOWER_PACKET_TYPE_CHASSIS_CONTROL_RQ);
        }
      else /* cmd_args.oem_power_type == IPMIPOWER_OEM_POWER_TYPE_C410X */
        {
          assert (ip->cmd == IPMIPOWER_POWER_CMD_POWER_STATUS
                  || ip->cmd == IPMIPOWER_POWER_CMD_POWER_OFF
                  || ip->cmd == IPMIPOWER_POWER_CMD_POWER_ON);

          _send_packet (ip, IPMIPOWER_PACKET_TYPE_C410X_GET_SENSOR_READING_RQ);
        }
    }
  else if (ip->protocol_state == IPMIPOWER_PROTOCOL_STATE_GET_CHASSIS_STATUS_SENT)
    {
      uint8_t power_state;

      if ((rv = _recv_packet (ip, IPMIPOWER_PACKET_TYPE_GET_CHASSIS_STATUS_RS)) != 1)
        {
          if (rv < 0)
            /* Session is up, so close it */
            _send_packet (ip, IPMIPOWER_PACKET_TYPE_CLOSE_SESSION_RQ);
          goto done;
        }

      if (FIID_OBJ_GET (ip->obj_get_chassis_status_rs,
                        "current_power_state.power_is_on",
                        &val) < 0)
        {
          IPMIPOWER_ERROR (("FIID_OBJ_GET: 'current_power_state.power_is_on': %s",
                            fiid_obj_errormsg (ip->obj_get_chassis_status_rs)));
          exit (EXIT_FAILURE);
        }
      power_state = val;

      if (cmd_args.wait_until_on
          && ip->cmd == IPMIPOWER_POWER_CMD_POWER_ON
          && ip->wait_until_on_state)
        {
          if (power_state == IPMI_SYSTEM_POWER_IS_ON)
            {
              ipmipower_output (IPMIPOWER_MSG_TYPE_OK, ip->ic->hostname, ip->extra_arg);
              ip->wait_until_on_state = 0;
              _send_packet (ip, IPMIPOWER_PACKET_TYPE_CLOSE_SESSION_RQ);
            }
        }
      else if (cmd_args.wait_until_off
               && ip->cmd == IPMIPOWER_POWER_CMD_POWER_OFF
               && ip->wait_until_off_state)
        {
          if (power_state == IPMI_SYSTEM_POWER_IS_OFF)
            {
              ipmipower_output (IPMIPOWER_MSG_TYPE_OK, ip->ic->hostname, ip->extra_arg);
              ip->wait_until_off_state = 0;
              _send_packet (ip, IPMIPOWER_PACKET_TYPE_CLOSE_SESSION_RQ);
            }
        }
      else if (ip->cmd == IPMIPOWER_POWER_CMD_POWER_STATUS)
        {
          ipmipower_output ((power_state == IPMI_SYSTEM_POWER_IS_ON) ? IPMIPOWER_MSG_TYPE_ON : IPMIPOWER_MSG_TYPE_OFF,
                            ip->ic->hostname,
                            ip->extra_arg);
          _send_packet (ip, IPMIPOWER_PACKET_TYPE_CLOSE_SESSION_RQ);
        }
      else if (cmd_args.on_if_off && (ip->cmd == IPMIPOWER_POWER_CMD_POWER_CYCLE
                                      || ip->cmd == IPMIPOWER_POWER_CMD_POWER_RESET))
        {
          if (!power_state)
            {
              /* This is now a power-on operation */
              ip->cmd = IPMIPOWER_POWER_CMD_POWER_ON;
            }
          _send_packet (ip, IPMIPOWER_PACKET_TYPE_CHASSIS_CONTROL_RQ);
        }
      else if (ip->cmd == IPMIPOWER_POWER_CMD_IDENTIFY_STATUS)
        {
          uint8_t identify_status_supported;

          if (FIID_OBJ_GET (ip->obj_get_chassis_status_rs,
                            "misc_chassis_state.chassis_identify_command_and_state_info_supported",
                            &val) < 0)
            {
              IPMIPOWER_ERROR (("FIID_OBJ_GET: 'misc_chassis_state.chassis_identify_command_and_state_info_supported': %s",
                                fiid_obj_errormsg (ip->obj_get_chassis_status_rs)));
              exit (EXIT_FAILURE);
            }
          identify_status_supported = val;

          if (identify_status_supported)
            {
              uint8_t identify_status;

              if (FIID_OBJ_GET (ip->obj_get_chassis_status_rs,
                                "misc_chassis_state.chassis_identify_state",
                                &val) < 0)
                {
                  IPMIPOWER_ERROR (("FIID_OBJ_GET: 'misc_chassis_state.chassis_identify_state': %s",
                                    fiid_obj_errormsg (ip->obj_get_chassis_status_rs)));
                  exit (EXIT_FAILURE);
                }
              identify_status = val;

              if (identify_status == IPMI_CHASSIS_IDENTIFY_STATE_OFF)
                ipmipower_output (IPMIPOWER_MSG_TYPE_OFF, ip->ic->hostname, ip->extra_arg);
              else if (identify_status == IPMI_CHASSIS_IDENTIFY_STATE_TEMPORARY_ON
                       || identify_status == IPMI_CHASSIS_IDENTIFY_STATE_INDEFINITE_ON)
                ipmipower_output (IPMIPOWER_MSG_TYPE_ON, ip->ic->hostname, ip->extra_arg);
              else
                ipmipower_output (IPMIPOWER_MSG_TYPE_UNKNOWN, ip->ic->hostname, ip->extra_arg);
            }
          else
            ipmipower_output (IPMIPOWER_MSG_TYPE_UNKNOWN, ip->ic->hostname, ip->extra_arg);

          _send_packet (ip, IPMIPOWER_PACKET_TYPE_CLOSE_SESSION_RQ);
        }
      else
        {
          IPMIPOWER_ERROR (("_process_ipmi_packets: invalid command state: %d", ip->cmd));
          exit (EXIT_FAILURE);
        }
    }
  else if (ip->protocol_state == IPMIPOWER_PROTOCOL_STATE_CHASSIS_CONTROL_SENT)
    {
      if ((rv = _recv_packet (ip, IPMIPOWER_PACKET_TYPE_CHASSIS_CONTROL_RS)) != 1)
        {
          if (rv < 0)
            /* Session is up, so close it */
            _send_packet (ip, IPMIPOWER_PACKET_TYPE_CLOSE_SESSION_RQ);
          goto done;
        }

      if ((cmd_args.wait_until_on
           && ip->cmd == IPMIPOWER_POWER_CMD_POWER_ON)
          || (cmd_args.wait_until_off
              && ip->cmd == IPMIPOWER_POWER_CMD_POWER_OFF))
        {
          if (ip->cmd == IPMIPOWER_POWER_CMD_POWER_ON)
            ip->wait_until_on_state++;
          else
            ip->wait_until_off_state++;
          _send_packet (ip, IPMIPOWER_PACKET_TYPE_GET_CHASSIS_STATUS_RQ);
        }
      else
        {
          ipmipower_output (IPMIPOWER_MSG_TYPE_OK, ip->ic->hostname, ip->extra_arg);

          /* IPMI Workaround (achu)
           *
           * Discovered on Intel Tiger4 (SR870BN4)
           *
           * There is no response from the IPMI close command if the
           * IPMIPOWER_POWER_CMD_POWER_RESET power control command is
           * successful.  So just skip the close session.
           */
          if (ip->cmd == IPMIPOWER_POWER_CMD_POWER_RESET)
            goto finish_up;
          else
            _send_packet (ip, IPMIPOWER_PACKET_TYPE_CLOSE_SESSION_RQ);
        }
    }
  else if (ip->protocol_state == IPMIPOWER_PROTOCOL_STATE_CHASSIS_IDENTIFY_SENT)
    {
      if ((rv = _recv_packet (ip, IPMIPOWER_PACKET_TYPE_CHASSIS_IDENTIFY_RS)) != 1)
        {
          if (rv < 0)
            /* Session is up, so close it */
            _send_packet (ip, IPMIPOWER_PACKET_TYPE_CLOSE_SESSION_RQ);
          goto done;
        }

      ipmipower_output (IPMIPOWER_MSG_TYPE_OK, ip->ic->hostname, ip->extra_arg);
      _send_packet (ip, IPMIPOWER_PACKET_TYPE_CLOSE_SESSION_RQ);
    }
  else if (ip->protocol_state == IPMIPOWER_PROTOCOL_STATE_C410X_GET_SENSOR_READING_SENT)
    {
      uint8_t sensor_reading;
      uint8_t reading_state;
      uint8_t sensor_scanning;
      int slot_power_on_flag;

      if ((rv = _recv_packet (ip, IPMIPOWER_PACKET_TYPE_C410X_GET_SENSOR_READING_RS)) != 1)
        {
          if (rv < 0)
            /* Session is up, so close it */
            _send_packet (ip, IPMIPOWER_PACKET_TYPE_CLOSE_SESSION_RQ);
          goto done;
        }

      if (FIID_OBJ_GET (ip->obj_c410x_get_sensor_reading_rs,
                        "sensor_reading",
                        &val) < 0)
        {
          IPMIPOWER_ERROR (("FIID_OBJ_GET: 'sensor_reading': %s",
                            fiid_obj_errormsg (ip->obj_get_chassis_status_rs)));
          exit (EXIT_FAILURE);
        }
      sensor_reading = val;

      if (FIID_OBJ_GET (ip->obj_c410x_get_sensor_reading_rs,
                        "reading_state",
                        &val) < 0)
        {
          IPMIPOWER_ERROR (("FIID_OBJ_GET: 'reading_state': %s",
                            fiid_obj_errormsg (ip->obj_get_chassis_status_rs)));
          exit (EXIT_FAILURE);
        }
      reading_state = val;

      if (FIID_OBJ_GET (ip->obj_c410x_get_sensor_reading_rs,
                        "sensor_scanning",
                        &val) < 0)
        {
          IPMIPOWER_ERROR (("FIID_OBJ_GET: 'sensor_scanning': %s",
                            fiid_obj_errormsg (ip->obj_get_chassis_status_rs)));
          exit (EXIT_FAILURE);
        }
      sensor_scanning = val;

      if (reading_state == IPMI_SENSOR_READING_STATE_UNAVAILABLE
          || sensor_scanning == IPMI_SENSOR_SCANNING_ON_THIS_SENSOR_DISABLE)
        {
          ipmipower_output (IPMIPOWER_MSG_TYPE_BMC_ERROR, ip->ic->hostname, ip->extra_arg);
          _send_packet (ip, IPMIPOWER_PACKET_TYPE_CLOSE_SESSION_RQ);
          goto done;
        }

      /* If non-zero, then it's on */
      /* achu: Sometimes "off" is 2.0 Watts, which equates to a sensor reading of 1 */
      if (sensor_reading > 1)
        slot_power_on_flag = 1;
      else
        slot_power_on_flag = 0;

      if (cmd_args.wait_until_on
          && ip->cmd == IPMIPOWER_POWER_CMD_POWER_ON
          && ip->wait_until_on_state)
        {
          if (slot_power_on_flag)
            {
              ipmipower_output (IPMIPOWER_MSG_TYPE_OK, ip->ic->hostname, ip->extra_arg);
              ip->wait_until_on_state = 0;
              _send_packet (ip, IPMIPOWER_PACKET_TYPE_CLOSE_SESSION_RQ);
            }
        }
      else if (cmd_args.wait_until_off
               && ip->cmd == IPMIPOWER_POWER_CMD_POWER_OFF
               && ip->wait_until_off_state)
        {
          if (!slot_power_on_flag)
            {
              ipmipower_output (IPMIPOWER_MSG_TYPE_OK, ip->ic->hostname, ip->extra_arg);
              ip->wait_until_off_state = 0;
              _send_packet (ip, IPMIPOWER_PACKET_TYPE_CLOSE_SESSION_RQ);
            }
        }
      else if (ip->cmd == IPMIPOWER_POWER_CMD_POWER_STATUS)
        {
          ipmipower_output ((slot_power_on_flag) ? IPMIPOWER_MSG_TYPE_ON : IPMIPOWER_MSG_TYPE_OFF,
                            ip->ic->hostname,
                            ip->extra_arg);
          _send_packet (ip, IPMIPOWER_PACKET_TYPE_CLOSE_SESSION_RQ);
        }
      else if (ip->cmd == IPMIPOWER_POWER_CMD_POWER_ON)
        {
          if (slot_power_on_flag)
            {
              ipmipower_output (IPMIPOWER_MSG_TYPE_OK, ip->ic->hostname, ip->extra_arg);
              _send_packet (ip, IPMIPOWER_PACKET_TYPE_CLOSE_SESSION_RQ);
            }
          else
            _send_packet (ip, IPMIPOWER_PACKET_TYPE_C410X_SLOT_POWER_CONTROL_RQ);
        }
      else if (ip->cmd == IPMIPOWER_POWER_CMD_POWER_OFF)
        {
          if (!slot_power_on_flag)
            {
              ipmipower_output (IPMIPOWER_MSG_TYPE_OK, ip->ic->hostname, ip->extra_arg);
              _send_packet (ip, IPMIPOWER_PACKET_TYPE_CLOSE_SESSION_RQ);
            }
          else
            _send_packet (ip, IPMIPOWER_PACKET_TYPE_C410X_SLOT_POWER_CONTROL_RQ);
        }
      else
        {
          IPMIPOWER_ERROR (("_process_ipmi_packets: invalid command state: %d", ip->cmd));
          exit (EXIT_FAILURE);
        }
    }
  else if (ip->protocol_state == IPMIPOWER_PROTOCOL_STATE_C410X_SLOT_POWER_CONTROL_SENT)
    {
      if ((rv = _recv_packet (ip, IPMIPOWER_PACKET_TYPE_C410X_SLOT_POWER_CONTROL_RS)) != 1)
        {
          if (rv < 0)
            /* Session is up, so close it */
            _send_packet (ip, IPMIPOWER_PACKET_TYPE_CLOSE_SESSION_RQ);
          goto done;
        }

      if ((cmd_args.wait_until_on
           && ip->cmd == IPMIPOWER_POWER_CMD_POWER_ON)
          || (cmd_args.wait_until_off
              && ip->cmd == IPMIPOWER_POWER_CMD_POWER_OFF))
        {
          if (ip->cmd == IPMIPOWER_POWER_CMD_POWER_ON)
            ip->wait_until_on_state++;
          else
            ip->wait_until_off_state++;
          _send_packet (ip, IPMIPOWER_PACKET_TYPE_C410X_GET_SENSOR_READING_RQ);
        }
      else
        {
          ipmipower_output (IPMIPOWER_MSG_TYPE_OK, ip->ic->hostname, ip->extra_arg);
          _send_packet (ip, IPMIPOWER_PACKET_TYPE_CLOSE_SESSION_RQ);
        }
    }
  else if (ip->protocol_state == IPMIPOWER_PROTOCOL_STATE_CLOSE_SESSION_SENT)
    {
      /* achu: Note that it's possible we're timing out too early and
       * the close session response will still arrive.  It's no
       * matter.  If we are in non-interactive mode, the file
       * descriptor will be closed and the packet lost.  If we are in
       * interactive mode, the next power control command will call
       * 'ipmipower_connection_clear' and get rid of the packet if it
       * is sitting on a buf.
       */
      if (ip->close_timeout)
        {
          IPMIPOWER_DEBUG (("host = %s; p = %d; close session timeout, skip retransmission",
                            ip->ic->hostname,
                            ip->protocol_state));
          goto finish_up;
        }

      if (!_recv_packet (ip, IPMIPOWER_PACKET_TYPE_CLOSE_SESSION_RS))
        goto done;

      /* Regardless of packet error or success, finish up */
    finish_up:
      ip->protocol_state = IPMIPOWER_PROTOCOL_STATE_END;
      return (-1); /* don't goto done and calculate timeout */
    }
  else
    {
      IPMIPOWER_ERROR (("_process_ipmi_packets: invalid state: %d", ip->protocol_state));
      exit (EXIT_FAILURE);
    }

 done:
  if (gettimeofday (&cur_time, NULL) < 0)
    {
      IPMIPOWER_ERROR (("gettimeofday: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }
  timeval_add_ms (&(ip->time_begin), cmd_args.common_args.session_timeout, &end_time);
  timeval_sub (&end_time, &cur_time, &result);
  timeval_millisecond_calc (&result, &timeout);

  /* shorter timeout b/c of retransmission timeout */
  if ((ip->wait_until_on_state && ip->cmd == IPMIPOWER_POWER_CMD_POWER_ON)
      || (ip->wait_until_off_state && ip->cmd == IPMIPOWER_POWER_CMD_POWER_OFF))
    {
      int retransmission_wait_timeout = cmd_args.retransmission_wait_timeout * (1 + (ip->retransmission_count/cmd_args.retransmission_backoff_count));
      if (timeout > retransmission_wait_timeout)
        timeout = retransmission_wait_timeout;
    }
  else
    {
      int retransmission_timeout = cmd_args.common_args.retransmission_timeout * (1 + (ip->retransmission_count/cmd_args.retransmission_backoff_count));
      if (timeout > retransmission_timeout)
        timeout = retransmission_timeout;
    }

  return (timeout);
}

int
ipmipower_powercmd_process_pending (int *timeout)
{
  ListIterator itr;
  ipmipower_powercmd_t ip;
  int min_timeout = cmd_args.common_args.session_timeout;
  int num_pending;

  assert (pending);  /* did not run ipmipower_powercmd_setup() */
  assert (timeout);

  /* if there are no pending jobs, don't edit the timeout */
  if (list_is_empty (pending))
    return (0);

  /* If we have a fanout, powercmds should be executed "in order" on
   * this list.  So no need to iterate through this list twice.
   */

  if (!(itr = list_iterator_create (pending)))
    {
      IPMIPOWER_ERROR (("list_iterator_create: %s", strerror (errno)));
      exit (EXIT_FAILURE);
    }

  while ((ip = (ipmipower_powercmd_t)list_next (itr)))
    {
      int tmp_timeout = -1;

      if ((tmp_timeout = _process_ipmi_packets (ip)) < 0)
        {
          if (cmd_args.oem_power_type == IPMIPOWER_OEM_POWER_TYPE_C410X)
            {
              if (ip->next)
                {
                  if (!list_append (add_to_pending, ip->next))
                    {
                      IPMIPOWER_ERROR (("list_append: %s", strerror (errno)));
                      exit (EXIT_FAILURE);
                    }

                  ip->next = NULL;
                }
            }

          if (!list_delete (itr))
            {
              IPMIPOWER_ERROR (("list_delete"));
              exit (EXIT_FAILURE);
            }

          executing_count--;
          continue;
        }

      if (tmp_timeout < min_timeout)
        min_timeout = tmp_timeout;
    }
  list_iterator_destroy (itr);

  if (list_count (add_to_pending) > 0)
    {
      ListIterator addtoitr;

      if (!(addtoitr = list_iterator_create (add_to_pending)))
        {
          IPMIPOWER_ERROR (("list_iterator_create: %s", strerror (errno)));
          exit (EXIT_FAILURE);
        }
      while ((ip = list_next (addtoitr)))
        {
          ipmipower_connection_clear (ip->ic);
          if (!list_append (pending, ip))
            {
              IPMIPOWER_ERROR (("list_append: %s", strerror (errno)));
              exit (EXIT_FAILURE);
            }

          if (!list_delete (addtoitr))
            {
              IPMIPOWER_ERROR (("list_delete"));
              exit (EXIT_FAILURE);
            }
        }

      list_iterator_destroy (addtoitr);

      /* If by chance all commands are going to the same host, then
       * the next powercmd will start after a default timeout.  We
       * don't want that.  We'll shorten the timeout to a
       * retransmission timeout so it appears more normal.
       */

      if (cmd_args.common_args.retransmission_timeout < min_timeout)
        min_timeout = cmd_args.common_args.retransmission_timeout;
    }

  if (!(num_pending = list_count (pending)))
    ipmipower_output_finish ();

  /* If the last pending power control command finished, the timeout
   * is 0 to get the primary poll loop to "re-init" at the start of
   * the loop.
   */
  if (num_pending)
    *timeout = min_timeout;
  else
    *timeout = 0;
  return (num_pending);
}
