/*
 * ipmi_lan.c
 *
 * MontaVista IPMI code for handling IPMI LAN connections
 *
 * Author: MontaVista Software, Inc.
 *         Corey Minyard <minyard@mvista.com>
 *         source@mvista.com
 *
 * Copyright 2002,2003,2004 MontaVista Software Inc.
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public License
 *  as published by the Free Software Foundation; either version 2 of
 *  the License, or (at your option) any later version.
 *
 *
 *  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
 *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 *  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 *  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 *  TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 *  USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this program; if not, write to the Free
 *  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <config.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/stat.h>
#include <sys/poll.h>
#include <sys/time.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <netdb.h>
#include <arpa/inet.h>

#include <OpenIPMI/ipmi_conn.h>
#include <OpenIPMI/ipmi_msgbits.h>
#include <OpenIPMI/ipmi_auth.h>
#include <OpenIPMI/ipmi_err.h>
#include <OpenIPMI/ipmi_lan.h>

#include <OpenIPMI/internal/ipmi_event.h>
#include <OpenIPMI/internal/ipmi_int.h>
#include <OpenIPMI/internal/locked_list.h>

#if defined(DEBUG_MSG) || defined(DEBUG_RAWMSG)
static void
dump_hex(void *vdata, int len)
{
    unsigned char *data = vdata;
    int i;
    for (i=0; i<len; i++) {
	if ((i != 0) && ((i % 16) == 0)) {
	    ipmi_log(IPMI_LOG_DEBUG_CONT, "\n  ");
	}
	ipmi_log(IPMI_LOG_DEBUG_CONT, " %2.2x", data[i]);
    }
}
#endif

#define LAN_AUDIT_TIMEOUT 10000000

/* Timeout to wait for IPMI responses, in microseconds.  For commands
   with side effects, we wait 5 seconds, not one. */
#define LAN_RSP_TIMEOUT 1000000
#define LAN_RSP_TIMEOUT_SIDEEFF 5000000

/* # of times to try a message before we fail it. */
#define LAN_RSP_RETRIES 6

/* Number of microseconds of consecutive failures allowed on an IP
   before it is considered failed. */
#define IP_FAIL_TIME 7000000

/* Number of consecutive failures that must occur before an IP is
   considered failed. */
#define IP_FAIL_COUNT 4

/* The default for the maximum number of messages that are allowed to be
   outstanding.  This is a pretty conservative number. */
#define DEFAULT_MAX_OUTSTANDING_MSG_COUNT 2
#define MAX_POSSIBLE_OUTSTANDING_MSG_COUNT 63

typedef struct lan_data_s lan_data_t;

typedef struct audit_timer_info_s
{
    int        cancelled;
    ipmi_con_t *ipmi;
} audit_timer_info_t;

typedef struct lan_timer_info_s
{
    int               cancelled;
    ipmi_con_t        *ipmi;
    os_hnd_timer_id_t *timer;
    unsigned int      seq;
} lan_timer_info_t;

typedef struct lan_wait_queue_s
{
    lan_timer_info_t      *info;
    ipmi_addr_t           addr;
    unsigned int          addr_len;
    ipmi_msg_t            msg;
    unsigned char         data[IPMI_MAX_MSG_LENGTH];
    ipmi_ll_rsp_handler_t rsp_handler;
    ipmi_msgi_t           *rsp_item;
    int                   side_effects;

    struct lan_wait_queue_s *next;
} lan_wait_queue_t;

#define MAX_IP_ADDR 2

/* We must keep this number small, if it's too big and a failure
   occurs, we will be outside the sequence number before we switch. */
#define SENDS_BETWEEN_IP_SWITCHES 3

/* Because sizeof(sockaddr_in6) > sizeof(sockaddr_in), this structure
 * is used as a replacement of struct sockaddr. */
typedef struct sockaddr_ip_s {
    union
        {
	    struct sockaddr	s_addr;
            struct sockaddr_in  s_addr4;
#ifdef PF_INET6
            struct sockaddr_in6 s_addr6;
#endif
        } s_ipsock;
    socklen_t ip_addr_len;
} sockaddr_ip_t;

struct ipmi_rmcpp_auth_s
{
    lan_data_t *lan;
    int        addr_num;

    uint8_t       role;

    /* Filled in by the auth algorithm. */
    unsigned char my_rand[16];
    unsigned int  my_rand_len;
    unsigned char mgsys_rand[16];
    unsigned int  mgsys_rand_len;
    unsigned char mgsys_guid[16];
    unsigned int  mgsys_guid_len;
    unsigned char sik[20];
    unsigned int  sik_len;
    unsigned char k1[20];
    unsigned int  k1_len;
    unsigned char k2[20];
    unsigned int  k2_len;
};

typedef struct lan_conn_parms_s
{
    unsigned int  num_ip_addr;
    char          *ip_addr_str[MAX_IP_ADDR];
    char          *ip_port_str[MAX_IP_ADDR];
    sockaddr_ip_t ip_addr[MAX_IP_ADDR];
    unsigned int  authtype;
    unsigned int  privilege;
    unsigned char username[IPMI_USERNAME_MAX];
    unsigned int  username_len;
    unsigned char password[IPMI_PASSWORD_MAX];
    unsigned int  password_len;
    unsigned int  conf;
    unsigned int  integ;
    unsigned int  auth;
    unsigned int  name_lookup_only;
    unsigned char bmc_key[IPMI_PASSWORD_MAX];
    unsigned int  bmc_key_len;
} lan_conn_parms_t;

typedef struct lan_link_s lan_link_t;
struct lan_link_s
{
    lan_link_t *next, *prev;
    lan_data_t *lan;
};

typedef struct lan_fd_s lan_fd_t;

typedef struct lan_stat_info_s
{
#define STAT_RECV_PACKETS	0
#define STAT_XMIT_PACKETS	1
#define STAT_REXMITS		2
#define STAT_TIMED_OUT		3
#define STAT_INVALID_RMCP	4
#define STAT_TOO_SHORT		5
#define STAT_INVALID_AUTH	6
#define STAT_BAD_SESSION_ID	7
#define STAT_AUTH_FAIL		8
#define STAT_DUPLICATES		9
#define STAT_SEQ_OUT_OF_RANGE	10
#define STAT_ASYNC_EVENTS	11
#define STAT_CONN_DOWN		12
#define STAT_CONN_UP		13
#define STAT_BAD_SIZE		14
#define STAT_DECRYPT_FAIL	15
#define STAT_INVALID_PAYLOAD	16
#define STAT_SEQ_ERR		17
#define STAT_RSP_NO_CMD		18
#define NUM_STATS 19
    /* Statistics */
    void *stats[NUM_STATS];
} lan_stat_info_t;

static const char *lan_stat_names[NUM_STATS] =
{
    "lan_recv_packets",
    "lan_xmit_packets",
    "lan_rexmits",
    "lan_timed_out",
    "lan_invalid_rmcp",
    "lan_too_short",
    "lan_invalid_auth",
    "lan_bad_session_id",
    "lan_auth_fail",
    "lan_duplicates",
    "lan_seq_out_of_range",
    "lan_async_events",
    "lan_conn_down",
    "lan_conn_up",
    "lan_bad_size",
    "lan_decrypt_fail",
    "lan_invalid_payload",
    "lan_seq_err",
    "lan_rsp_no_cmd"
};


/* Per-IP specific information. */
typedef struct lan_ip_data_s
{
    int                        working;
    unsigned int               consecutive_failures;
    struct timeval             failure_time;

    /* For both RMCP and RMCP+.  For RMCP+, the session id is the one
       I receive and the sequence numbers are the authenticated
       ones. */
    unsigned char              working_authtype;
    uint32_t                   session_id;
    uint32_t                   outbound_seq_num;
    uint32_t                   inbound_seq_num;
    uint16_t                   recv_msg_map;

    /* RMCP+ specific info */
    uint32_t                   unauth_out_seq_num;
    uint32_t                   unauth_in_seq_num;
    uint16_t                   unauth_recv_msg_map;
    unsigned char              working_integ;
    unsigned char              working_conf;
    uint32_t                   mgsys_session_id;
    ipmi_rmcpp_auth_t          ainfo;

    /* Used to hold the session id before the connection is up. */
    uint32_t                   precon_session_id;
    uint32_t                   precon_mgsys_session_id;

    ipmi_rmcpp_confidentiality_t *conf_info;
    void                         *conf_data;

    ipmi_rmcpp_integrity_t       *integ_info;
    void                         *integ_data;

    /* Use for linked-lists of IP addresses. */
    lan_link_t                 ip_link;
} lan_ip_data_t;


#if IPMI_MAX_MSG_LENGTH > 80
# define LAN_MAX_RAW_MSG IPMI_MAX_MSG_LENGTH
#else
# define LAN_MAX_RAW_MSG 80 /* Enough to hold the rmcp+ session messages */
#endif
struct lan_data_s
{
    unsigned int	       refcount;
    unsigned int	       users;

    ipmi_con_t                 *ipmi;
    lan_fd_t                   *fd;
    int                        fd_slot;

    unsigned char              slave_addr[MAX_IPMI_USED_CHANNELS];
    int                        is_active;

    /* Have we already been started? */
    int                        started;

    /* Are we currently in cleanup?  Don't allow any outgoing messages. */
    int                        in_cleanup;

    /* Protects modifiecations to working, curr_ip_addr, RMCP
       sequence numbers, the con_change_handler, and other
       connection-related data.  Note that if the seq_num_lock must
       also be held, it must be locked before this lock.  */
    ipmi_lock_t                *ip_lock;

    /* If 0, we don't have a connection to the BMC right now. */
    int                        connected;

    /* If 0, we have not yet initialized */
    int                        initialized;

    /* If 0, the OEM handlers have not been called. */
    int                        oem_conn_handlers_called;

    /* Number of packets sent on the connection.  Used to track when
       to switch between IP addresses. */
    unsigned int               num_sends;

    /* The IP address we are currently using. */
    unsigned int               curr_ip_addr;

    /* Data about each IP address */
    lan_ip_data_t              ip[MAX_IP_ADDR];

    /* We keep a session on each LAN connection.  I don't think all
       systems require that, but it's safer. */

    /* From the get channel auth */
    unsigned char              oem_iana[3];
    unsigned char              oem_aux;

    /* Parms we were configured with. */
    lan_conn_parms_t           cparm;

    /* IPMI LAN 1.5 specific info. */
    unsigned char              chosen_authtype;
    unsigned char              challenge_string[16];
    ipmi_authdata_t            authdata;

    /* RMCP+ specific info */
    unsigned int               use_two_keys : 1;

    struct {
	unsigned int          inuse : 1;
	ipmi_addr_t           addr;
	unsigned int          addr_len;
	
	ipmi_msg_t            msg;
	unsigned char         data[LAN_MAX_RAW_MSG];
	ipmi_ll_rsp_handler_t rsp_handler;
	ipmi_msgi_t           *rsp_item;
	int                   use_orig_addr;
	ipmi_addr_t           orig_addr;
	unsigned int          orig_addr_len;
	os_hnd_timer_id_t     *timer;
	lan_timer_info_t      *timer_info;
	int                   retries_left;
	int                   side_effects;

	/* If -1, just use the normal algorithm.  If not -1, force to
           this address. */
	int                   addr_num;

	/* The number of the last IP address sent on. */
	int                   last_ip_num;
    } seq_table[64];
    ipmi_lock_t               *seq_num_lock;

    /* The current sequence number.  Note that we reserve sequence
       number 0 for our own neferous purposes. */
    unsigned int              last_seq;

    /* The number of messages that are outstanding with the remote
       MC. */
    unsigned int outstanding_msg_count;

    /* The maximum number of outstanding messages.  This must NEVER be
       larger than 63 (64 sequence numbers minus 1 for our reserved
       sequence zero. */
    unsigned int max_outstanding_msg_count;

    /* List of messages waiting to be sent. */
    lan_wait_queue_t *wait_q, *wait_q_tail;

    locked_list_t              *event_handlers;

    os_hnd_timer_id_t          *audit_timer;
    audit_timer_info_t         *audit_info;

    /* Handles connection shutdown reporting. */
    ipmi_ll_con_closed_cb close_done;
    void                  *close_cb_data;

    /* This lock is used to assure that the conn changes occur in
       proper order.  The user code is called with this lock held, but
       it should be harmless to the user as this is the only use for
       it.  But the user cannot do a wait on I/O in the handler. */
    ipmi_lock_t            *con_change_lock;
    locked_list_t          *con_change_handlers;

    locked_list_t          *ipmb_change_handlers;

    lan_link_t link;

    locked_list_t *lan_stat_list;
};


/************************************************************************
 *
 * Authentication and encryption information and functions.
 *
 ***********************************************************************/
extern ipmi_payload_t _ipmi_payload;

static int
open_format_msg(ipmi_con_t        *ipmi,
		const ipmi_addr_t *addr,
		unsigned int      addr_len,
		const ipmi_msg_t  *msg,
		unsigned char     *out_data,
		unsigned int      *out_data_len,
		int               *out_of_session,
		unsigned char     seq)
{
    unsigned char *tmsg = out_data;

    if (msg->data_len > *out_data_len)
	return E2BIG;

    memcpy(tmsg, msg->data, msg->data_len);
    tmsg[0] = seq; /* We use the message tag for the sequence # */
    *out_of_session = 1;
    *out_data_len = msg->data_len;
    return 0;
}

static int
open_get_recv_seq(ipmi_con_t    *ipmi,
		  unsigned char *data,
		  unsigned int  data_len,
		  unsigned char *seq)
{
    if (data_len < 1) { /* Minimum size of an IPMI msg. */
	if (DEBUG_RAWMSG || DEBUG_MSG_ERR)
	    ipmi_log(IPMI_LOG_DEBUG,
		     "%sDropped message because too small(7)",
		     IPMI_CONN_NAME(ipmi));
	return EINVAL;
    }
    *seq = data[0];
    return 0;
}

static int
open_handle_recv(ipmi_con_t    *ipmi,
		 ipmi_msgi_t   *rspi,
		 ipmi_addr_t   *orig_addr,
		 unsigned int  orig_addr_len,
		 ipmi_msg_t    *orig_msg,
		 unsigned char *data,
		 unsigned int  data_len)
{
    ipmi_msg_t *msg = &(rspi->msg);
    if (data_len > sizeof(rspi->data))
	return E2BIG;
    memcpy(rspi->data, data, data_len);
    msg->data = rspi->data;
    msg->data_len = data_len;
    return 0;
}

static void
open_handle_recv_async(ipmi_con_t    *ipmi,
		       unsigned char *tmsg,
		       unsigned int  data_len)
{
}

static int
open_get_msg_tag(unsigned char *tmsg,
		 unsigned int  data_len,
		 unsigned char *tag)
{
    if (data_len < 8)
	return EINVAL;
    *tag = ipmi_get_uint32(tmsg+4) - 1; /* session id */
    return 0;
}

static ipmi_payload_t open_payload =
{ open_format_msg, open_get_recv_seq, open_handle_recv,
  open_handle_recv_async, open_get_msg_tag };

static ipmi_payload_t *payloads[64] =
{
    &_ipmi_payload,
    [IPMI_RMCPP_PAYLOAD_TYPE_OPEN_SESSION_REQUEST] = &open_payload,
    [IPMI_RMCPP_PAYLOAD_TYPE_OPEN_SESSION_RESPONSE] = &open_payload
};

typedef struct payload_entry_s payload_entry_t;
struct payload_entry_s
{
    unsigned int   payload_type;
    unsigned char  iana[3];
    unsigned int   payload_id;
    ipmi_payload_t *payload;

    payload_entry_t *next;
};

/* Note that we only add payloads to the head, so no lock is required
   except for addition. */
static ipmi_lock_t *lan_payload_lock = NULL;
payload_entry_t *oem_payload_list = NULL;

int
ipmi_rmcpp_register_payload(unsigned int   payload_type,
			    ipmi_payload_t *payload)
{
    if ((payload_type == IPMI_RMCPP_PAYLOAD_TYPE_IPMI)
	|| (payload_type == IPMI_RMCPP_PAYLOAD_TYPE_OEM_EXPLICIT)
	|| (payload_type == IPMI_RMCPP_PAYLOAD_TYPE_OPEN_SESSION_REQUEST)
	|| (payload_type == IPMI_RMCPP_PAYLOAD_TYPE_OPEN_SESSION_RESPONSE)
	|| (payload_type >= 64)
	|| ((payload_type >= 0x20) && (payload_type <= 0x27))) /* No OEM here*/
    {
	return EINVAL;
    }
    ipmi_lock(lan_payload_lock);
    if (payloads[payload_type] && payload) {
	ipmi_unlock(lan_payload_lock);
	return EAGAIN;
    }

    payloads[payload_type] = payload;
    ipmi_unlock(lan_payload_lock);
    return 0;
}

int
ipmi_rmcpp_register_oem_payload(unsigned int   payload_type,
				unsigned char  iana[3],
				unsigned int   payload_id,
				ipmi_payload_t *payload)
{
    payload_entry_t *e;
    payload_entry_t *c;

    e = ipmi_mem_alloc(sizeof(*e));
    if (!e)
	return ENOMEM;
    e->payload_type = payload_type;
    memcpy(e->iana, iana, 3);
    if (payload_type == IPMI_RMCPP_PAYLOAD_TYPE_OEM_EXPLICIT)
	e->payload_id = payload_id;
    else
	e->payload_id = 0;
    e->payload = payload;

    ipmi_lock(lan_payload_lock);
    c = oem_payload_list;
    while (c) {
	if ((c->payload_type == payload_type)
	    && (memcmp(c->iana, iana, 3) == 0)
	    && (c->payload_id == payload_id))
	{
	    ipmi_unlock(lan_payload_lock);
	    ipmi_mem_free(e);
	    return EAGAIN;
	}
	c = c->next;
    }
    e->next = oem_payload_list;
    oem_payload_list = e;
    ipmi_unlock(lan_payload_lock);
    return 0;
}

static ipmi_lock_t *lan_auth_lock = NULL;

typedef struct auth_entry_s auth_entry_t;
struct auth_entry_s
{
    unsigned int  auth_num;
    unsigned char iana[3];
    ipmi_rmcpp_authentication_t *auth;
    auth_entry_t  *next;
};
static auth_entry_t *oem_auth_list = NULL;

static ipmi_rmcpp_authentication_t *auths[64];

int
ipmi_rmcpp_register_authentication(unsigned int                auth_num,
				   ipmi_rmcpp_authentication_t *auth)
{
    if (auth_num >= 64)
	return EINVAL;
    if (auths[auth_num] && auth)
	return EAGAIN;
    
    auths[auth_num] = auth;
    return 0;
}

int
ipmi_rmcpp_register_oem_authentication(unsigned int                auth_num,
				       unsigned char               iana[3],
				       ipmi_rmcpp_authentication_t *auth)
{
    auth_entry_t *e;
    auth_entry_t *c;

    e = ipmi_mem_alloc(sizeof(*e));
    if (!e)
	return ENOMEM;
    e->auth_num = auth_num;
    memcpy(e->iana, iana, 3);
    e->auth = auth;

    ipmi_lock(lan_auth_lock);
    c = oem_auth_list;
    while (c) {
	if ((c->auth_num == auth_num)
	    && (memcmp(c->iana, iana, 3) == 0))
	{
	    ipmi_unlock(lan_auth_lock);
	    ipmi_mem_free(e);
	    return EAGAIN;
	}
    }
    e->next = oem_auth_list;
    oem_auth_list = e;
    ipmi_unlock(lan_auth_lock);
    return 0;
}

typedef struct conf_entry_s conf_entry_t;
struct conf_entry_s
{
    unsigned int  conf_num;
    unsigned char iana[3];
    ipmi_rmcpp_confidentiality_t *conf;
    conf_entry_t  *next;
};
static conf_entry_t *oem_conf_list = NULL;

static int
conf_none_init(ipmi_con_t *ipmi, ipmi_rmcpp_auth_t *ainfo, void **conf_data)
{
    *conf_data = NULL;
    return 0;
}

static void
conf_none_free(ipmi_con_t *ipmi, void *conf_data)
{
}

static int
conf_none_encrypt(ipmi_con_t    *ipmi,
		  void          *conf_data,
		  unsigned char **payload,
		  unsigned int  *header_len,
		  unsigned int  *payload_len,
		  unsigned int  *max_payload_len)
{
    return 0;
}

static int
conf_none_decrypt(ipmi_con_t    *ipmi,
		  void          *conf_data,
		  unsigned char **payload,
		  unsigned int  *payload_len)
{
    return 0;
}
static ipmi_rmcpp_confidentiality_t conf_none =
{ conf_none_init, conf_none_free, conf_none_encrypt, conf_none_decrypt};

static ipmi_rmcpp_confidentiality_t *confs[64] =
{
    &conf_none
};

int ipmi_rmcpp_register_confidentiality(unsigned int                 conf_num,
					ipmi_rmcpp_confidentiality_t *conf)
{
    if ((conf_num == 0) || (conf_num >= 64))
	return EINVAL;
    if (confs[conf_num] && conf)
	return EAGAIN;
    
    confs[conf_num] = conf;
    return 0;
}

int
ipmi_rmcpp_register_oem_confidentiality(unsigned int                 conf_num,
					unsigned char                iana[3],
					ipmi_rmcpp_confidentiality_t *conf)
{
    conf_entry_t *e;
    conf_entry_t *c;

    e = ipmi_mem_alloc(sizeof(*e));
    if (!e)
	return ENOMEM;
    e->conf_num = conf_num;
    memcpy(e->iana, iana, 3);
    e->conf = conf;

    ipmi_lock(lan_auth_lock);
    c = oem_conf_list;
    while (c) {
	if ((c->conf_num == conf_num)
	    && (memcmp(c->iana, iana, 3) == 0))
	{
	    ipmi_unlock(lan_auth_lock);
	    ipmi_mem_free(e);
	    return EAGAIN;
	}
    }
    e->next = oem_conf_list;
    oem_conf_list = e;
    ipmi_unlock(lan_auth_lock);
    return 0;
}

typedef struct integ_entry_s integ_entry_t;
struct integ_entry_s
{
    unsigned int  integ_num;
    unsigned char iana[3];
    ipmi_rmcpp_integrity_t *integ;
    integ_entry_t  *next;
};
static integ_entry_t *oem_integ_list = NULL;

static int
integ_none_init(ipmi_con_t       *ipmi,
		ipmi_rmcpp_auth_t *ainfo,
		void             **integ_data)
{
    *integ_data = NULL;
    return 0;
}

static void
integ_none_free(ipmi_con_t *ipmi,
		void       *integ_data)
{
}

static int
integ_none_pad(ipmi_con_t    *ipmi,
	       void          *integ_data,
	       unsigned char *payload,
	       unsigned int  *payload_len,
	       unsigned int  max_payload_len)
{
    return 0;
}

static int
integ_none_add(ipmi_con_t    *ipmi,
	       void          *integ_data,
	       unsigned char *payload,
	       unsigned int  *payload_len,
	       unsigned int  max_payload_len)
{
    return 0;
}

static int
integ_none_check(ipmi_con_t    *ipmi,
		 void          *integ_data,
		 unsigned char *payload,
		 unsigned int  payload_len,
		 unsigned int  total_len)
{
    return 0;
}

static ipmi_rmcpp_integrity_t integ_none =
{ integ_none_init, integ_none_free, integ_none_pad, integ_none_add,
  integ_none_check };

static ipmi_rmcpp_integrity_t *integs[64] =
{
    &integ_none
};

int ipmi_rmcpp_register_integrity(unsigned int           integ_num,
				  ipmi_rmcpp_integrity_t *integ)
{
    if ((integ_num == 0) || (integ_num >= 64))
	return EINVAL;
    if (integs[integ_num] && integ)
	return EAGAIN;
    
    integs[integ_num] = integ;
    return 0;
}

int
ipmi_rmcpp_register_oem_integrity(unsigned int           integ_num,
				  unsigned char          iana[3],
				  ipmi_rmcpp_integrity_t *integ)
{
    integ_entry_t *e;
    integ_entry_t *c;

    e = ipmi_mem_alloc(sizeof(*e));
    if (!e)
	return ENOMEM;
    e->integ_num = integ_num;
    memcpy(e->iana, iana, 3);
    e->integ = integ;

    ipmi_lock(lan_auth_lock);
    c = oem_integ_list;
    while (c) {
	if ((c->integ_num == integ_num)
	    && (memcmp(c->iana, iana, 3) == 0))
	{
	    ipmi_unlock(lan_auth_lock);
	    ipmi_mem_free(e);
	    return EAGAIN;
	}
    }
    e->next = oem_integ_list;
    oem_integ_list = e;
    ipmi_unlock(lan_auth_lock);
    return 0;
}

uint32_t
ipmi_rmcpp_auth_get_my_session_id(ipmi_rmcpp_auth_t *ainfo)
{
    return ainfo->lan->ip[ainfo->addr_num].precon_session_id;
}

uint32_t
ipmi_rmcpp_auth_get_mgsys_session_id(ipmi_rmcpp_auth_t *ainfo)
{
    return ainfo->lan->ip[ainfo->addr_num].precon_mgsys_session_id;
}

uint8_t
ipmi_rmcpp_auth_get_role(ipmi_rmcpp_auth_t *ainfo)
{
    return ainfo->role;
}

const unsigned char *
ipmi_rmcpp_auth_get_username(ipmi_rmcpp_auth_t *ainfo,
			     unsigned int      *max_len)
{
    *max_len = 16;
    return ainfo->lan->cparm.username;
}

unsigned int
ipmi_rmcpp_auth_get_username_len(ipmi_rmcpp_auth_t *ainfo)
{
    return ainfo->lan->cparm.username_len;
}

const unsigned char *
ipmi_rmcpp_auth_get_password(ipmi_rmcpp_auth_t *ainfo,
			     unsigned int      *max_len)
{
    *max_len = 20;
    return ainfo->lan->cparm.password;
}

unsigned int
ipmi_rmcpp_auth_get_password_len(ipmi_rmcpp_auth_t *ainfo)
{
    return ainfo->lan->cparm.password_len;
}

int
ipmi_rmcpp_auth_get_use_two_keys(ipmi_rmcpp_auth_t *ainfo)
{
    return ainfo->lan->use_two_keys;
}

const unsigned char *
ipmi_rmcpp_auth_get_bmc_key(ipmi_rmcpp_auth_t *ainfo,
			    unsigned int      *max_len)
{
    *max_len = 20;
    if (ainfo->lan->use_two_keys)
	return ainfo->lan->cparm.bmc_key;
    else
	return ainfo->lan->cparm.password;
}

unsigned int
ipmi_rmcpp_auth_get_bmc_key_len(ipmi_rmcpp_auth_t *ainfo)
{
    if (ainfo->lan->use_two_keys)
	return ainfo->lan->cparm.bmc_key_len;
    else
	return ainfo->lan->cparm.password_len;
}

/* From the get channel auth. */
const unsigned char *
ipmi_rmcpp_auth_get_oem_iana(ipmi_rmcpp_auth_t *ainfo,
			     unsigned int      *len)
{
    *len = 3;
    return ainfo->lan->oem_iana;
}

unsigned char
ipmi_rmcpp_auth_get_oem_aux(ipmi_rmcpp_auth_t *ainfo)
{
    return ainfo->lan->oem_aux;
}

/* Should be filled in by the auth algorithm. */
unsigned char *
ipmi_rmcpp_auth_get_my_rand(ipmi_rmcpp_auth_t *ainfo,
			    unsigned int      *max_len)
{
    *max_len = 16;
    return ainfo->my_rand;
}

unsigned int
ipmi_rmcpp_auth_get_my_rand_len(ipmi_rmcpp_auth_t *ainfo)
{
    return ainfo->my_rand_len;
}

void
ipmi_rmcpp_auth_set_my_rand_len(ipmi_rmcpp_auth_t *ainfo,
				unsigned int      length)
{
    ainfo->my_rand_len = length;
}

unsigned char *
ipmi_rmcpp_auth_get_mgsys_rand(ipmi_rmcpp_auth_t *ainfo,
			       unsigned int      *max_len)
{
    *max_len = 16;
    return ainfo->mgsys_rand;
}

unsigned int
ipmi_rmcpp_auth_get_mgsys_rand_len(ipmi_rmcpp_auth_t *ainfo)
{
    return ainfo->mgsys_rand_len;
}

void
ipmi_rmcpp_auth_set_mgsys_rand_len(ipmi_rmcpp_auth_t *ainfo,
				   unsigned int      length)
{
    ainfo->mgsys_rand_len = length;
}

unsigned char *
ipmi_rmcpp_auth_get_mgsys_guid(ipmi_rmcpp_auth_t *ainfo,
			       unsigned int      *max_len)
{
    *max_len = 16;
    return ainfo->mgsys_guid;
}

unsigned int
ipmi_rmcpp_auth_get_mgsys_guid_len(ipmi_rmcpp_auth_t *ainfo)
{
    return ainfo->mgsys_guid_len;
}

void
ipmi_rmcpp_auth_set_mgsys_guid_len(ipmi_rmcpp_auth_t *ainfo,
				   unsigned int      length)
{
    ainfo->mgsys_guid_len = length;
}

unsigned char *
ipmi_rmcpp_auth_get_sik(ipmi_rmcpp_auth_t *ainfo,
			unsigned int      *max_len)
{
    *max_len = 20;
    return ainfo->sik;
}

unsigned int
ipmi_rmcpp_auth_get_sik_len(ipmi_rmcpp_auth_t *ainfo)
{
    return ainfo->sik_len;
}

void
ipmi_rmcpp_auth_set_sik_len(ipmi_rmcpp_auth_t *ainfo,
			    unsigned int      length)
{
    ainfo->sik_len = length;
}

unsigned char *
ipmi_rmcpp_auth_get_k1(ipmi_rmcpp_auth_t *ainfo,
		       unsigned int      *max_len)
{
    *max_len = 20;
    return ainfo->k1;
}

unsigned int
ipmi_rmcpp_auth_get_k1_len(ipmi_rmcpp_auth_t *ainfo)
{
    return ainfo->k1_len;
}

void
ipmi_rmcpp_auth_set_k1_len(ipmi_rmcpp_auth_t *ainfo,
			   unsigned int      length)
{
    ainfo->k1_len = length;
}

unsigned char *
ipmi_rmcpp_auth_get_k2(ipmi_rmcpp_auth_t *ainfo,
		       unsigned int      *max_len)
{
    *max_len = 20;
    return ainfo->k2;
}

unsigned int
ipmi_rmcpp_auth_get_k2_len(ipmi_rmcpp_auth_t *ainfo)
{
    return ainfo->k2_len;
}

void
ipmi_rmcpp_auth_set_k2_len(ipmi_rmcpp_auth_t *ainfo,
			   unsigned int      length)
{
    ainfo->k2_len = length;
}


static void check_command_queue(ipmi_con_t *ipmi, lan_data_t *lan);
static int send_auth_cap(ipmi_con_t *ipmi, lan_data_t *lan, int addr_num,
			 int force_ipmiv15);

static os_handler_t *lan_os_hnd;

#define MAX_CONS_PER_FD	32
struct lan_fd_s
{
    int            fd;
    os_hnd_fd_id_t *fd_wait_id;
    unsigned int   cons_in_use;
    lan_data_t     *lan[MAX_CONS_PER_FD];
    lan_fd_t       *next, *prev;
    ipmi_lock_t    *con_lock;

    /* Main list info. */
    ipmi_lock_t    *lock;
    lan_fd_t       **free_list;
    lan_fd_t       *list;
};

/* This is a list, but the only searching is to find an fd with a free
   slot (when creating a new lan).  This is O(1) because the first
   entry is guaranteed to have a free slot if any have free slots.
   Note that once one of these is created, it is never destroyed
   (destruction is very difficult because of the race conditions). */
static ipmi_lock_t *fd_list_lock = NULL;
static lan_fd_t fd_list;
static lan_fd_t *fd_free_list;
#ifdef PF_INET6
static ipmi_lock_t *fd6_list_lock = NULL;
static lan_fd_t fd6_list;
static lan_fd_t *fd6_free_list;
#endif

static void data_handler(int            fd,
			 void           *cb_data,
			 os_hnd_fd_id_t *id);

static int
lan_addr_same(sockaddr_ip_t *a1, sockaddr_ip_t *a2)
{
    if (a1->ip_addr_len != a2->ip_addr_len)
	return 0;

    if (a1->s_ipsock.s_addr.sa_family != a2->s_ipsock.s_addr.sa_family) {
	if (DEBUG_RAWMSG || DEBUG_MSG_ERR)
	    ipmi_log(IPMI_LOG_DEBUG, "Address family mismatch: %d %d",
		     a1->s_ipsock.s_addr.sa_family,
		     a2->s_ipsock.s_addr.sa_family);
	return 0;
    }

    switch (a1->s_ipsock.s_addr.sa_family) {
    case PF_INET:
	{
	    struct sockaddr_in *ip1 = &a1->s_ipsock.s_addr4;
	    struct sockaddr_in *ip2 = &a2->s_ipsock.s_addr4;

	    if ((ip1->sin_port == ip2->sin_port)
		&& (ip1->sin_addr.s_addr == ip2->sin_addr.s_addr))
		return 1;
	}
	break;

#ifdef PF_INET6
    case PF_INET6:
	{
	    struct sockaddr_in6 *ip1 = &a1->s_ipsock.s_addr6;
	    struct sockaddr_in6 *ip2 = &a2->s_ipsock.s_addr6;
	    if ((ip1->sin6_port == ip2->sin6_port)
		&& (bcmp(ip1->sin6_addr.s6_addr, ip2->sin6_addr.s6_addr,
			 sizeof(struct in6_addr)) == 0))
		return 1;
	}
	break;
#endif
    default:
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "ipmi_lan: Unknown protocol family: 0x%x",
		 a1->s_ipsock.s_addr.sa_family);
	break;
    }

    return 0;
}

static void
move_to_lan_list_end(lan_fd_t *item)
{
    lan_fd_t *list = item->list;

    item->next->prev = item->prev;
    item->prev->next = item->next;
    item->next = list;
    item->prev = list->prev;
    list->prev->next = item;
    list->prev = item;
}

static void
move_to_lan_list_head(lan_fd_t *item)
{
    lan_fd_t *list = item->list;

    item->next->prev = item->prev;
    item->prev->next = item->next;
    item->next = list->next;
    item->prev = list;
    list->next->prev = item;
    list->next = item;
}

static lan_fd_t *
find_free_lan_fd(int family, lan_data_t *lan, int *slot)
{
    ipmi_lock_t *lock;
    lan_fd_t    *list, *item;
    lan_fd_t    **free_list;
    int         rv;
    int         i;

    if (family == PF_INET) {
	lock = fd_list_lock;
	list = &fd_list;
	free_list = &fd_free_list;
    }
#ifdef PF_INET6
    else if (family == PF_INET6) {
	lock = fd6_list_lock;
	list = &fd6_list;
	free_list = &fd6_free_list;
    }
#endif
    else {
	return NULL;
    }

    ipmi_lock(lock);
    item = list->next;
 retry:
    if (item->cons_in_use < MAX_CONS_PER_FD) {
	int tslot = -1;
	/* Got an entry with a slot, just reuse it. */
	for (i=0; i<MAX_CONS_PER_FD; i++) {
	    if (item->lan[i]) {
		/* Check for a matching IP address.  Can't have two
		   systems with the same address in the same fd entry. */
		unsigned int j, k;
		lan_data_t   *l = item->lan[i];

		for (j=0; j<l->cparm.num_ip_addr; j++) {
		    for (k=0; k<lan->cparm.num_ip_addr; k++) {
			if (lan_addr_same(&l->cparm.ip_addr[j],
					  &lan->cparm.ip_addr[k]))
			{
			    /* Found the same address in the same
			       lan_data file.  Try another one. */
			    item = item->next;
			    goto retry;
			}
		    }
		}
	    } else if (tslot < 0)
		tslot = i;
	}
	if (tslot < 0) {
	    lan_fd_t *next = item->next;
	    /* Can't happen, but log and fix it up. */
	    ipmi_log(IPMI_LOG_SEVERE, "ipmi_lan.c: Internal error, count"
		     " in lan fd list item incorrect, but we can recover.");
	    item->cons_in_use = MAX_CONS_PER_FD;
	    move_to_lan_list_end(item);
	    item = next;
	    goto retry;
	}
	item->cons_in_use++;
	item->lan[tslot] = lan;
	*slot = tslot;

	if (item->cons_in_use == MAX_CONS_PER_FD)
	    /* Out of connections in this item, move it to the end of
	       the list. */
	    move_to_lan_list_end(item);
    } else {
	/* No free entries, create one */
	if (*free_list) {
	    /* Pull them off the free list first. */
	    item = *free_list;
	    *free_list = item->next;
	} else {
	    item = ipmi_mem_alloc(sizeof(*item));
	    if (item) {
		memset(item, 0, sizeof(*item));
		rv = ipmi_create_global_lock(&item->con_lock);
		if (rv) {
		    ipmi_mem_free(item);
		    goto out_unlock;
		}
		item->lock = lock;
		item->free_list = free_list;
		item->list = list;
	    }
	}
	if (!item)
	    goto out_unlock;

	item->next = item;
	item->prev = item;

	item->fd = socket(family, SOCK_DGRAM, IPPROTO_UDP);
	if (item->fd == -1) {
	    item->next = *free_list;
	    *free_list = item;
	    item = NULL;
	    goto out_unlock;
	}

	/* Bind is not necessary, we don't care what port we are. */

	/* We want it to be non-blocking. */
	rv = fcntl(item->fd, F_SETFL, O_NONBLOCK);
	if (rv) {
	    close(item->fd);
	    item->next = *free_list;
	    *free_list = item;
	    item = NULL;
	    goto out_unlock;
	}

	rv = lan_os_hnd->add_fd_to_wait_for(lan_os_hnd,
					    item->fd,
					    data_handler, 
					    item,
					    NULL,
					    &(item->fd_wait_id));
	if (rv) {
	    close(item->fd);
	    item->next = *free_list;
	    *free_list = item;
	    item = NULL;
	    goto out_unlock;
	}

	item->cons_in_use++;
	item->lan[0] = lan;
	*slot = 0;

	/* This will have free items, put it at the head of the list. */
	move_to_lan_list_head(item);
    }
 out_unlock:
    ipmi_unlock(lock);
    return item;
}

static void
release_lan_fd(lan_fd_t *item, int slot)
{
    ipmi_lock(item->lock);
    item->lan[slot] = NULL;
    item->cons_in_use--;
    if (item->cons_in_use == 0) {
	lan_os_hnd->remove_fd_to_wait_for(lan_os_hnd, item->fd_wait_id);
	close(item->fd);
	item->next->prev = item->prev;
	item->prev->next = item->next;
	item->next = *(item->free_list);
	*(item->free_list) = item;
    } else {
	/* This has free connections, move it to the head of the
	   list. */
	move_to_lan_list_head(item);
    }
    ipmi_unlock(item->lock);
}

/*
 * We keep two hash tables, one by IP address and one by connection
 * address.
 */
#define LAN_HASH_SIZE 256
#define LAN_HASH_SHIFT 6
static ipmi_lock_t *lan_list_lock = NULL;
static lan_link_t lan_list[LAN_HASH_SIZE];
static lan_link_t lan_ip_list[LAN_HASH_SIZE];

static unsigned int
hash_lan(const ipmi_con_t *ipmi)
{
    unsigned int idx;

    idx = (((unsigned long) ipmi)
	   >> (sizeof(unsigned long) + LAN_HASH_SHIFT));
    idx %= LAN_HASH_SIZE;
    return idx;
}

static int
hash_lan_addr(const struct sockaddr *addr)
{
    int idx;
    switch (addr->sa_family)
    {
    case PF_INET:
	{
	    struct sockaddr_in *iaddr = (struct sockaddr_in *) addr;
	    idx = ntohl(iaddr->sin_addr.s_addr) % LAN_HASH_SIZE;
	    break;
	}
#ifdef PF_INET6
    case PF_INET6:
	{
	    /* Use the lower 4 bytes of the IPV6 address. */
	    struct sockaddr_in6 *iaddr = (struct sockaddr_in6 *) addr;
	    idx = htonl(*((uint32_t *) &iaddr->sin6_addr.s6_addr[12]));
	    idx %= LAN_HASH_SIZE;
	    break;
	}
#endif
    default:
	idx = 0;
    }
    idx %= LAN_HASH_SIZE;
    return idx;
}

static void
lan_add_con(lan_data_t *lan)
{
    unsigned int idx;
    lan_link_t   *head;
    unsigned int i;

    ipmi_lock(lan_list_lock);
    idx = hash_lan(lan->ipmi);
    head = &lan_list[idx];
    lan->link.lan = lan;
    lan->link.next = head;
    lan->link.prev = head->prev;
    head->prev->next = &lan->link;
    head->prev = &lan->link;

    for (i=0; i<lan->cparm.num_ip_addr; i++) {
	struct sockaddr *addr = &lan->cparm.ip_addr[i].s_ipsock.s_addr;

	idx = hash_lan_addr(addr);

	head = &lan_ip_list[idx];
	lan->ip[i].ip_link.lan = lan;
	lan->ip[i].ip_link.next = head;
	lan->ip[i].ip_link.prev = head->prev;
	head->prev->next = &lan->ip[i].ip_link;
	head->prev = &lan->ip[i].ip_link;
    }
    ipmi_unlock(lan_list_lock);
}

/* Must be called with the lan list lock held. */
static void
lan_remove_con_nolock(lan_data_t *lan)
{
    unsigned int i;
    if (!lan->link.lan)
	/* Hasn't been initialized. */
	return;
    lan->link.prev->next = lan->link.next;
    lan->link.next->prev = lan->link.prev;
    lan->link.lan = NULL;
    for (i=0; i<lan->cparm.num_ip_addr; i++) {
	lan->ip[i].ip_link.prev->next = lan->ip[i].ip_link.next;
	lan->ip[i].ip_link.next->prev = lan->ip[i].ip_link.prev;
	lan->ip[i].ip_link.lan = NULL;
    }
}

static lan_data_t *
lan_find_con(ipmi_con_t *ipmi)
{
    unsigned int idx;
    lan_link_t   *l;

    ipmi_lock(lan_list_lock);
    idx = hash_lan(ipmi);
    l = lan_list[idx].next;
    while (l->lan) {
	if (l->lan->ipmi == ipmi)
	    break;
	l = l->next;
    }
    if (l->lan)
	l->lan->refcount++;
    ipmi_unlock(lan_list_lock);

    return l->lan;
}

static inline int
cmp_timeval(struct timeval *tv1, struct timeval *tv2)
{
    if (tv1->tv_sec < tv2->tv_sec)
        return -1;
 
    if (tv1->tv_sec > tv2->tv_sec)
        return 1;

    if (tv1->tv_usec < tv2->tv_usec)
        return -1; 
   
    if (tv1->tv_usec > tv2->tv_usec)
        return 1;

    return 0;
}

typedef struct lan_add_stat_info_s
{
    int statnum;
    int count;
} lan_add_stat_info_t;

int
add_stat_cb(void *cb_data, void *item1, void *item2)
{
    ipmi_ll_stat_info_t *info = item2;
    lan_stat_info_t     *stat = item1;
    lan_add_stat_info_t *sinfo = cb_data;

    if (stat->stats[sinfo->statnum])
	ipmi_ll_con_stat_call_adder(info, stat->stats[sinfo->statnum],
				    sinfo->count);
    return LOCKED_LIST_ITER_CONTINUE;
}

static inline void
add_stat(ipmi_con_t *ipmi, int stat, int count)
{
    lan_data_t          *lan = ipmi->con_data;
    lan_add_stat_info_t sinfo;

    sinfo.statnum = stat;
    sinfo.count = count;
    locked_list_iterate(lan->lan_stat_list, add_stat_cb, &sinfo);
}

static inline void
diff_timeval(struct timeval *dest,
             struct timeval *left,
             struct timeval *right)
{
    if (   (left->tv_sec < right->tv_sec)
        || (   (left->tv_sec == right->tv_sec)
            && (left->tv_usec < right->tv_usec)))
    {
        /* If left < right, just force to zero, don't allow negative
           numbers. */
        dest->tv_sec = 0;
        dest->tv_usec = 0;
        return;
    }

    dest->tv_sec = left->tv_sec - right->tv_sec;
    dest->tv_usec = left->tv_usec - right->tv_usec;
    while (dest->tv_usec < 0) {
        dest->tv_usec += 1000000;
        dest->tv_sec--;
    }
}

/* Must be called with the ipmi read or write lock. */
static int lan_valid_ipmi(ipmi_con_t *ipmi)
{
    return (lan_find_con(ipmi) != NULL);
}

static void lan_cleanup(ipmi_con_t *ipmi);

static void
lan_put(ipmi_con_t *ipmi)
{
    lan_data_t *lan = ipmi->con_data;
    int        done;

    ipmi_lock(lan_list_lock);
    lan->refcount--;
    done = lan->refcount == 0;

    /* If done, remove it before we release the lock. */
    if (done)
	lan_remove_con_nolock(lan);
    ipmi_unlock(lan_list_lock);

    if (done)
	lan_cleanup(ipmi);
}

static int
auth_gen(lan_data_t    *lan,
	 unsigned char *out,
	 uint8_t       *ses_id,
	 uint8_t       *seq,
	 unsigned char *data,
	 unsigned int  data_len,
	 int           addr_num)
{
    int rv;
    ipmi_auth_sg_t l[] =
    { { ses_id, 4 },
      { data,   data_len },
      { seq,    4 },
      { NULL,   0 }};

    rv = ipmi_auths[lan->ip[addr_num].working_authtype]
	.authcode_gen(lan->authdata, l, out);
    return rv;
}

static int
auth_check(lan_data_t    *lan,
	   uint8_t       *ses_id,
	   uint8_t       *seq,
	   unsigned char *data,
	   unsigned int  data_len,
	   unsigned char *code,
	   int           addr_num)
{
    int rv;
    ipmi_auth_sg_t l[] =
    { { ses_id, 4  },
      { data,   data_len },
      { seq,    4 },
      { NULL,   0 }};

    rv = ipmi_auths[lan->ip[addr_num].working_authtype]
	.authcode_check(lan->authdata, l, code);
    return rv;
}

#define IPMI_MAX_LAN_LEN    (IPMI_MAX_MSG_LENGTH + 128)
#define IPMI_LAN_MAX_HEADER 128

static int
rmcpp_format_msg(lan_data_t *lan, int addr_num,
		 unsigned int payload_type, int in_session,
		 unsigned char **msgdata, unsigned int *data_len,
		 unsigned int  max_data_len, unsigned int header_len,
		 unsigned char *oem_iana, unsigned int oem_payload_id,
		 const ipmi_con_option_t *options)
{
    unsigned char *tmsg;
    int           rv;
    unsigned int  header_used;
    unsigned char *data;
    unsigned int  payload_len;
    uint32_t      *seqp;
    int           do_auth = 1;
    int           do_conf = 1;

    if (options) {
	while (options->option != IPMI_CON_OPTION_LIST_END) {
	    switch (options->option) {
	    case IPMI_CON_MSG_OPTION_AUTH:
		do_auth = options->ival;
		break;

	    case IPMI_CON_MSG_OPTION_CONF:
		do_conf = options->ival;
		break;

	    default:
		/* Ignore unknown options. */
		break;
	    }
	    options++;
	}
    }

    do_conf = (do_conf && in_session
	       && (lan->ip[addr_num].working_conf
		   != IPMI_LANP_CONFIDENTIALITY_ALGORITHM_NONE));
    do_auth = (do_auth && in_session
	       && (lan->ip[addr_num].working_integ
		   != IPMI_LANP_INTEGRITY_ALGORITHM_NONE));

    if (do_conf) {
#if 0
	if (! lan->ip[addr_num].working)
	    return EAGAIN;
#endif

	/* Note: This may encrypt the data, the old data will be lost. */
	rv = lan->ip[addr_num].conf_info->conf_encrypt
	    (lan->ipmi,
	     lan->ip[addr_num].conf_data,
	     msgdata,
	     &header_len, data_len,
	     &max_data_len);
	if (rv)
	    return rv;
    }

    payload_len = *data_len;

    if (payload_type == IPMI_RMCPP_PAYLOAD_TYPE_OEM_EXPLICIT)
	header_used = 22;
    else
	header_used = 16;

    if (header_used > header_len)
	return E2BIG;

    data = *msgdata - header_used;
    *data_len += header_used;
    max_data_len += header_used;

    data[0] = 6; /* RMCP version 1.0. */
    data[1] = 0;
    data[2] = 0xff;
    data[3] = 0x07;
    data[4] = lan->ip[addr_num].working_authtype;
    data[5] = payload_type;
    tmsg = data+6;
    if (payload_type == IPMI_RMCPP_PAYLOAD_TYPE_OEM_EXPLICIT) {
	memcpy(tmsg, oem_iana, 3);
	tmsg += 3;
	*tmsg = 0;
	tmsg++;
	ipmi_set_uint16(tmsg, oem_payload_id);
	tmsg += 2;
    }
    if (in_session) {
	if (do_conf)
	    data[5] |= 0x80;
	if (do_auth) {
	    seqp = &(lan->ip[addr_num].outbound_seq_num);
	    data[5] |= 0x40;
	} else {
	    seqp = &(lan->ip[addr_num].unauth_out_seq_num);
	}
	ipmi_set_uint32(tmsg, lan->ip[addr_num].mgsys_session_id);
	tmsg += 4;
	ipmi_set_uint32(tmsg, *seqp);
	tmsg += 4;
    } else {
	ipmi_set_uint32(tmsg, 0); /* session id */
	tmsg += 4;
	ipmi_set_uint32(tmsg, 0); /* session sequence number */
	tmsg += 4;
	seqp = NULL;
    }

    /* Payload length doesn't include the padding. */
    ipmi_set_uint16(tmsg, payload_len);

    if (do_auth) {
	rv = lan->ip[addr_num].integ_info->integ_pad
	    (lan->ipmi,
	     lan->ip[addr_num].integ_data,
	     data, data_len,
	     max_data_len);
	if (rv)
	    return rv;

	rv = lan->ip[addr_num].integ_info->integ_add
	    (lan->ipmi,
	     lan->ip[addr_num].integ_data,
	     data, data_len,
	     max_data_len);
	if (rv)
	    return rv;
    }

    if (seqp) {
	(*seqp)++;
	if (*seqp == 0)
	    *seqp = 1;
    }

    *msgdata = data;

    return 0;
}

static int
lan15_format_msg(lan_data_t *lan, int addr_num,
		 unsigned char **msgdata, unsigned int *data_len)
{
    unsigned char *data;
    int           rv;

    if (lan->ip[addr_num].working_authtype == IPMI_AUTHTYPE_NONE)
	data = *msgdata - 14;
    else
	data = *msgdata - 30;

    data[0] = 6; /* RMCP version 1.0. */
    data[1] = 0;
    data[2] = 0xff;
    data[3] = 0x07;
    data[4] = lan->ip[addr_num].working_authtype;
    ipmi_set_uint32(data+5, lan->ip[addr_num].outbound_seq_num);
    ipmi_set_uint32(data+9, lan->ip[addr_num].session_id);

    /* FIXME - need locks for the sequence numbers. */

    /* Increment the outbound number, but make sure it's not zero.  If
       it's already zero, ignore it, we are in pre-setup. */
    if (lan->ip[addr_num].outbound_seq_num != 0) {
	(lan->ip[addr_num].outbound_seq_num)++;
	if (lan->ip[addr_num].outbound_seq_num == 0)
	    (lan->ip[addr_num].outbound_seq_num)++;
    }

    if (lan->ip[addr_num].working_authtype == IPMI_AUTHTYPE_NONE) {
	/* No authentication, so no authcode. */
	data[13] = *data_len;
	*data_len += 14;
    } else {
	data[29] = *data_len;
	rv = auth_gen(lan, data+13, data+9, data+5, *msgdata, *data_len,
		      addr_num);
	if (rv)
	    return rv;
	*data_len += 30;
    }
    *msgdata = data;

    return 0;
}

static int
lan_send_addr(lan_data_t              *lan,
	      const ipmi_addr_t       *addr,
	      int                     addr_len,
	      const ipmi_msg_t        *msg,
	      uint8_t                 seq,
	      int                     addr_num,
	      const ipmi_con_option_t *options)
{
    unsigned char  data[IPMI_MAX_LAN_LEN+IPMI_LAN_MAX_HEADER];
    unsigned char  *tmsg;
    unsigned int   pos;
    int            rv;
    unsigned int   payload_type;
    int            out_of_session = 0;
    ipmi_payload_t *payload = NULL;
    unsigned char  oem_iana[3] = {0, 0, 0};
    unsigned int   oem_payload_id = 0;

    if ((addr->addr_type >= IPMI_RMCPP_ADDR_START)
	&& (addr->addr_type <= IPMI_RMCPP_ADDR_END))
    {
	/*
	 * Let through the dodgy IPMI 1.5 Serial-over-LAN packets, but block
	 * anything else that tries to send an RMCP+ packet to a non-RMCP+
	 * host.
	 */
	if ((addr->addr_type != IPMI_RMCPP_ADDR_SOL)
		&& (lan->ip[addr_num].working_authtype != IPMI_AUTHTYPE_RMCP_PLUS))
	    return EINVAL;
	payload_type = addr->addr_type - IPMI_RMCPP_ADDR_START;
    } else {
	switch (addr->addr_type) {
	case IPMI_SYSTEM_INTERFACE_ADDR_TYPE:
	case IPMI_IPMB_ADDR_TYPE:
	case IPMI_IPMB_BROADCAST_ADDR_TYPE:
	    payload_type = IPMI_RMCPP_PAYLOAD_TYPE_IPMI;
	    break;
	default:
	    return EINVAL;
	}
    }

    if ((payload_type == IPMI_RMCPP_PAYLOAD_TYPE_OEM_EXPLICIT)
	|| ((payload_type >= 0x20) && (payload_type <= 0x27)))
    {
	ipmi_rmcpp_addr_t *addr = (ipmi_rmcpp_addr_t *) addr;
	payload_entry_t *e;

	if (payload_type == IPMI_RMCPP_PAYLOAD_TYPE_OEM_EXPLICIT) {
	    memcpy(oem_iana, addr->oem_iana, 3);
	    oem_payload_id = addr->oem_payload_id;
	} else {
	    memcpy(oem_iana, lan->oem_iana, 3);
	    oem_payload_id = 0;
	}

	/* No lock required, only payload additions are allowed. */
	e = oem_payload_list;
	while (e) {
	    if ((e->payload_type == payload_type)
		&& (memcmp(e->iana, oem_iana, 3) == 0)
		&& (e->payload_id == oem_payload_id))
	    {
		payload = e->payload;
		break;
	    }
	    e = e->next;
	}
    } else {
	payload = payloads[payload_type];
    }

    tmsg = data + IPMI_LAN_MAX_HEADER;
    if (!payload) {
	return ENOSYS;
    } else {
	pos = IPMI_MAX_LAN_LEN;
	rv = payload->format_for_xmit(lan->ipmi, addr, addr_len,
				      msg, tmsg, &pos,
				      &out_of_session, seq);
	if (rv)
	    return rv;
    }

    if (lan->ip[addr_num].working_authtype == IPMI_AUTHTYPE_RMCP_PLUS) {
	rv = rmcpp_format_msg(lan, addr_num,
			      payload_type, !out_of_session,
			      &tmsg, &pos,
			      IPMI_MAX_LAN_LEN, IPMI_LAN_MAX_HEADER,
			      oem_iana, oem_payload_id, options);
    } else {
	rv = lan15_format_msg(lan, addr_num, &tmsg, &pos);
	if (addr->addr_type == IPMI_RMCPP_ADDR_SOL)
		/*
		 * We're sending SoL over IPMI 1.5, which requires that we set
		 * a "reserved" bit.  This is dodgy.
		 */
		tmsg[4] |= 0x80;
    }
    if (rv)
	return rv;

    if (DEBUG_RAWMSG) {
	char buf1[32], buf2[32];
	ipmi_log(IPMI_LOG_DEBUG_START, "%soutgoing seq %d\n addr =",
		 IPMI_CONN_NAME(lan->ipmi), seq);
	dump_hex((unsigned char *) &(lan->cparm.ip_addr[addr_num]),
		 sizeof(sockaddr_ip_t));
        ipmi_log(IPMI_LOG_DEBUG_CONT,
                 "\n msg  = netfn=%s cmd=%s data_len=%d.",
		 ipmi_get_netfn_string(msg->netfn, buf1, 32),
                 ipmi_get_command_string(msg->netfn, msg->cmd, buf2, 32),
		 msg->data_len);
	if (pos) {
	    ipmi_log(IPMI_LOG_DEBUG_CONT, "\n data =\n  ");
	    dump_hex(tmsg, pos);
	}
	ipmi_log(IPMI_LOG_DEBUG_END, " ");
    }

    add_stat(lan->ipmi, STAT_XMIT_PACKETS, 1);

    rv = sendto(lan->fd->fd, tmsg, pos, 0,
		(struct sockaddr *) &(lan->cparm.ip_addr[addr_num].s_ipsock),
		lan->cparm.ip_addr[addr_num].ip_addr_len);
    if (rv == -1)
	rv = errno;
    else
	rv = 0;

    return rv;
}

static int
lan_send(lan_data_t              *lan,
	 const ipmi_addr_t       *addr,
	 int                     addr_len,
	 const ipmi_msg_t        *msg,
	 uint8_t                 seq,
	 int                     *send_ip_num,
	 const ipmi_con_option_t *options)
{
    int curr_ip_addr;

    ipmi_lock(lan->ip_lock);
    if (msg->netfn & 1) {
	/* For unacknowledged packets, don't switch addresses.  They
	   don't contribute to detecting that the link is down. */
	curr_ip_addr = lan->curr_ip_addr;
    } else if (lan->connected) {
	lan->num_sends++;

	/* We periodically switch between IP addresses, just to make sure
	   they are all operational. */
	if ((lan->num_sends % SENDS_BETWEEN_IP_SWITCHES) == 0) {
	    unsigned int addr_num = lan->curr_ip_addr + 1;
	    if (addr_num >= lan->cparm.num_ip_addr)
		addr_num = 0;
	    while (addr_num != lan->curr_ip_addr) {
		if (lan->ip[addr_num].working)
		    break;
		addr_num++;
		if (addr_num >= lan->cparm.num_ip_addr)
		    addr_num = 0;
	    }
	    lan->curr_ip_addr = addr_num;
	}
    } else {
	/* Just rotate between IP addresses if we are not yet connected */
	unsigned int addr_num = lan->curr_ip_addr + 1;
	if (addr_num >= lan->cparm.num_ip_addr)
	    addr_num = 0;
	lan->curr_ip_addr = addr_num;
    }
    curr_ip_addr = lan->curr_ip_addr;
    ipmi_unlock(lan->ip_lock);

    *send_ip_num = curr_ip_addr;

    return lan_send_addr(lan, addr, addr_len, msg, seq, curr_ip_addr, options);
}

typedef struct call_ipmb_change_handler_s
{
    lan_data_t   *lan;
    int           err;
    const unsigned char *ipmb_addr;
    unsigned int  num_ipmb_addr;
    int           active;
    unsigned int  hacks;
} call_ipmb_change_handler_t;

static int
call_ipmb_change_handler(void *cb_data, void *item1, void *item2)
{
    call_ipmb_change_handler_t *info = cb_data;
    ipmi_ll_ipmb_addr_cb       handler = item1;

    handler(info->lan->ipmi, info->err, info->ipmb_addr, info->num_ipmb_addr,
	    info->active, info->hacks, item2);
    return LOCKED_LIST_ITER_CONTINUE;
}

static void
call_ipmb_change_handlers(lan_data_t *lan, int err,
			  const unsigned char ipmb_addr[],
			  unsigned int num_ipmb_addr,
			  int active, unsigned int hacks)
{
    call_ipmb_change_handler_t info;

    info.lan = lan;
    info.err = err;
    info.ipmb_addr = ipmb_addr;
    info.num_ipmb_addr = num_ipmb_addr;
    info.active = active;
    info.hacks = hacks;
    locked_list_iterate(lan->ipmb_change_handlers, call_ipmb_change_handler,
			&info);
}

static int
lan_add_ipmb_addr_handler(ipmi_con_t           *ipmi,
			  ipmi_ll_ipmb_addr_cb handler,
			  void                 *cb_data)
{
    lan_data_t *lan = (lan_data_t *) ipmi->con_data;

    if (locked_list_add(lan->ipmb_change_handlers, handler, cb_data))
	return 0;
    else
	return ENOMEM;
}

static int
lan_remove_ipmb_addr_handler(ipmi_con_t           *ipmi,
			     ipmi_ll_ipmb_addr_cb handler,
			     void                 *cb_data)
{
    lan_data_t *lan = (lan_data_t *) ipmi->con_data;

    if (locked_list_remove(lan->ipmb_change_handlers, handler, cb_data))
	return 0;
    else
	return EINVAL;
}

static void
ipmb_handler(ipmi_con_t   *ipmi,
	     int          err,
	     const unsigned char ipmb_addr[],
	     unsigned int num_ipmb_addr,
	     int          active,
	     unsigned int hacks,
	     void         *cb_data)
{
    lan_data_t *lan;
    int        changed = 0;
    int        i;

    if (err)
	return;

    lan = (lan_data_t *) ipmi->con_data;

    for (i=0; i<MAX_IPMI_USED_CHANNELS; i++) {
	if (! ipmb_addr[i])
	    continue;
	if (ipmb_addr[i] != lan->slave_addr[i]) {
	    lan->slave_addr[i] = ipmb_addr[i];
	    ipmi->ipmb_addr[i] = ipmb_addr[i];
	    changed = 1;
	}
    }
    if (changed || (lan->is_active != active))  {
	lan->is_active = active;
	ipmi->hacks = hacks;
	call_ipmb_change_handlers(lan, err, ipmb_addr, num_ipmb_addr,
				  active, hacks);
    }
}

static void
audit_timeout_handler(void              *cb_data,
		      os_hnd_timer_id_t *id)
{
    audit_timer_info_t           *info = cb_data;
    ipmi_con_t                   *ipmi = info->ipmi;
    lan_data_t                   *lan;
    struct timeval               timeout;
    ipmi_msg_t                   msg;
    unsigned int                 i;
    ipmi_system_interface_addr_t si;
    int                          start_up[MAX_IP_ADDR];


    /* If we were cancelled, just free the data and ignore the call. */
    if (info->cancelled)
	goto out_done;

    if (!lan_valid_ipmi(ipmi))
	goto out_done;

    lan = ipmi->con_data;

    /* Send message to all addresses we think are down.  If the
       connection is down, this will bring it up, otherwise it
       will keep it alive. */
    ipmi_lock(lan->ip_lock);
    for (i=0; i<lan->cparm.num_ip_addr; i++)
	    start_up[i] = ! lan->ip[i].working;
    ipmi_unlock(lan->ip_lock);

    for (i=0; i<lan->cparm.num_ip_addr; i++) {
	if (start_up[i])
	    send_auth_cap(ipmi, lan, i, 0);
    }

    msg.netfn = IPMI_APP_NETFN;
    msg.cmd = IPMI_GET_DEVICE_ID_CMD;
    msg.data = NULL;
    msg.data_len = 0;
		
    /* Send a message to check the working of the interface. */
    if (ipmi->get_ipmb_addr) {
	/* If we have a way to query the IPMB address, do so
           periodically. */
	ipmi->get_ipmb_addr(ipmi, ipmb_handler, NULL);
    } else {
	si.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
	si.channel = 0xf;
	si.lun = 0;
	ipmi->send_command(ipmi, (ipmi_addr_t *) &si, sizeof(si),
			   &msg, NULL, NULL);
    }

    timeout.tv_sec = LAN_AUDIT_TIMEOUT / 1000000;
    timeout.tv_usec = LAN_AUDIT_TIMEOUT % 1000000;
    ipmi->os_hnd->start_timer(ipmi->os_hnd,
			      id,
			      &timeout,
			      audit_timeout_handler,
			      cb_data);

    /* Make sure the timer info doesn't get freed. */
    info = NULL;

    lan_put(ipmi);

 out_done:
    if (info) {
	ipmi->os_hnd->free_timer(ipmi->os_hnd, id);
	ipmi_mem_free(info);
    }
    return;
}

typedef struct call_con_change_handler_s
{
    lan_data_t  *lan;
    int          err;
    unsigned int port;
    int          any_port_up;
} call_con_change_handler_t;

static int
call_con_change_handler(void *cb_data, void *item1, void *item2)
{
    call_con_change_handler_t  *info = cb_data;
    ipmi_ll_con_changed_cb     handler = item1;

    handler(info->lan->ipmi, info->err, info->port, info->any_port_up, item2);
    return LOCKED_LIST_ITER_CONTINUE;
}

static void
call_con_change_handlers(lan_data_t *lan, int err, unsigned int port,
			 int any_port_up)
{
    call_con_change_handler_t info;

    info.lan = lan;
    info.err = err;
    info.port = port;
    info.any_port_up = any_port_up;
    locked_list_iterate(lan->con_change_handlers, call_con_change_handler,
			&info);
}

void
_ipmi_lan_con_change_lock(ipmi_con_t *ipmi)
{
    lan_data_t *lan = (lan_data_t *) ipmi->con_data;
    ipmi_lock(lan->ip_lock);
    ipmi_lock(lan->con_change_lock);
    ipmi_unlock(lan->ip_lock);
}

void
_ipmi_lan_con_change_unlock(ipmi_con_t *ipmi)
{
    lan_data_t *lan = (lan_data_t *) ipmi->con_data;
    ipmi_unlock(lan->con_change_lock);
}

void
_ipmi_lan_call_con_change_handlers(ipmi_con_t   *ipmi,
				   int          err,
				   unsigned int port)
{
    lan_data_t *lan = (lan_data_t *) ipmi->con_data;

    call_con_change_handlers(lan, err, port, lan->connected);
}

static int
lan_add_con_change_handler(ipmi_con_t             *ipmi,
			   ipmi_ll_con_changed_cb handler,
			   void                   *cb_data)
{
    lan_data_t *lan = (lan_data_t *) ipmi->con_data;

    if (locked_list_add(lan->con_change_handlers, handler, cb_data))
	return 0;
    else
	return ENOMEM;
}

static int
lan_remove_con_change_handler(ipmi_con_t             *ipmi,
			      ipmi_ll_con_changed_cb handler,
			      void                   *cb_data)
{
    lan_data_t *lan = (lan_data_t *) ipmi->con_data;

    if (locked_list_remove(lan->con_change_handlers, handler, cb_data))
	return 0;
    else
	return EINVAL;
}

static void
connection_up(lan_data_t *lan, int addr_num, int new_con)
{
    add_stat(lan->ipmi, STAT_CONN_UP, 1);

    ipmi_lock(lan->ip_lock);
    if ((! lan->ip[addr_num].working) && new_con) {
	lan->ip[addr_num].working = 1;

	ipmi_log(IPMI_LOG_INFO,
		 "%sipmi_lan.c(connection_up): "
		 "Connection %d to the BMC is up",
		 IPMI_CONN_NAME(lan->ipmi), addr_num);
    }

    if (new_con) {
	ipmi_log(IPMI_LOG_INFO,
		 "%sipmi_lan.c(connection_up): "
		 "Connection to the BMC restored",
		 IPMI_CONN_NAME(lan->ipmi));
	lan->curr_ip_addr = addr_num;
    }

    if (lan->connected) {
	ipmi_lock(lan->con_change_lock);
	ipmi_unlock(lan->ip_lock);
	call_con_change_handlers(lan, 0, addr_num, 1);
	ipmi_unlock(lan->con_change_lock);
    } else {
	ipmi_unlock(lan->ip_lock);
    }    
}

static void
reset_session_data(lan_data_t *lan, int addr_num)
{
    lan_ip_data_t *ip = &lan->ip[addr_num];

    ip->outbound_seq_num = 0;
    ip->inbound_seq_num = 0;
    ip->session_id = 0;
    ip->mgsys_session_id = 0;
    ip->precon_session_id = 0;
    ip->precon_mgsys_session_id = 0;
    ip->recv_msg_map = 0;
    ip->unauth_recv_msg_map = 0;
    ip->working_authtype = 0;
    ip->unauth_out_seq_num = 0;
    ip->unauth_in_seq_num = 0;
    if (ip->conf_data) {
	ip->conf_info->conf_free(lan->ipmi, ip->conf_data);
	ip->conf_data = NULL;
    }
    ip->conf_info = NULL;
    if (ip->integ_data) {
	ip->integ_info->integ_free(lan->ipmi, ip->integ_data);
	ip->integ_data = NULL;
    }
    ip->integ_info = NULL;
    ip->working_conf = IPMI_LANP_CONFIDENTIALITY_ALGORITHM_NONE;
    ip->working_integ = IPMI_LANP_INTEGRITY_ALGORITHM_NONE;
}

static void
lost_connection(lan_data_t *lan, unsigned int addr_num)
{
    unsigned int i;

    ipmi_lock(lan->ip_lock);
    if (! lan->ip[addr_num].working) {
	ipmi_unlock(lan->ip_lock);
	return;
    }

    add_stat(lan->ipmi, STAT_CONN_DOWN, 1);

    lan->ip[addr_num].working = 0;

    reset_session_data(lan, addr_num);

    ipmi_log(IPMI_LOG_WARNING,
	     "%sipmi_lan.c(lost_connection): "
	     "Connection %d to the BMC is down",
	     IPMI_CONN_NAME(lan->ipmi), addr_num);

    if (lan->curr_ip_addr == addr_num) {
	/* Scan to see if any address is operational. */
	for (i=0; i<lan->cparm.num_ip_addr; i++) {
	    if (lan->ip[i].working) {
		lan->curr_ip_addr = i;
		break;
	    }
	}

	if (i >= lan->cparm.num_ip_addr) {
	    /* There were no operational connections, report that. */
	    ipmi_log(IPMI_LOG_SEVERE,
		     "%sipmi_lan.c(lost_connection): "
		     "All connections to the BMC are down",
		     IPMI_CONN_NAME(lan->ipmi));

	    lan->connected = 0;
	}
    }

    {
	int connected = lan->connected;
	
	ipmi_lock(lan->con_change_lock);
	ipmi_unlock(lan->ip_lock);
	call_con_change_handlers(lan, ETIMEDOUT, addr_num, connected);
	ipmi_unlock(lan->con_change_lock);
    }
}

static void
rsp_timeout_handler(void              *cb_data,
		    os_hnd_timer_id_t *id)
{
    lan_timer_info_t      *info = cb_data;
    ipmi_con_t            *ipmi = info->ipmi;
    lan_data_t            *lan;
    int                   seq;
    ipmi_ll_rsp_handler_t handler;
    ipmi_msgi_t           *rspi;
    int                   ip_num = 0;
    int                   call_lost_con = 0;

    if (!lan_valid_ipmi(ipmi))
	return;

    lan = ipmi->con_data;
    seq = info->seq;

    ipmi_lock(lan->seq_num_lock);

    /* If we were cancelled, just free the data and ignore it. */
    if (info->cancelled) {
	ipmi_unlock(lan->seq_num_lock);
	goto out;
    }

    if (DEBUG_RAWMSG || DEBUG_MSG_ERR)
	ipmi_log(IPMI_LOG_DEBUG, "%sTimeout for seq #%d",
		 IPMI_CONN_NAME(ipmi), seq);

    if (! lan->seq_table[seq].inuse) {
	ipmi_unlock(lan->seq_num_lock);
	goto out;
    }

    if (DEBUG_RAWMSG) {
	ip_num = lan->seq_table[seq].last_ip_num;
	ipmi_log(IPMI_LOG_DEBUG,
		 "%sSeq #%d\n"
		 "  addr_type=%d, ip_num=%d, fails=%d\n"
		 "  fail_start_time=%ld.%6.6ld",
		 IPMI_CONN_NAME(ipmi), 
		 seq, lan->seq_table[seq].addr.addr_type,
		 lan->seq_table[seq].last_ip_num,
		 lan->ip[ip_num].consecutive_failures,
		 lan->ip[ip_num].failure_time.tv_sec,
		 lan->ip[ip_num].failure_time.tv_usec);
    }

    if (lan->seq_table[seq].addr.addr_type == IPMI_SYSTEM_INTERFACE_ADDR_TYPE)
    {
	/* We only count timeouts on messages to the system interface.
           Otherwise, if we sent a bunch of messages to the IPMB that
           timed out, we might trigger this code accidentally. */
	ip_num = lan->seq_table[seq].last_ip_num;
	ipmi_lock(lan->ip_lock);
	if (lan->ip[ip_num].working) {
	    if (lan->ip[ip_num].consecutive_failures == 0) {
		/* Set the time when the connection will be considered
                   failed. */
		gettimeofday(&(lan->ip[ip_num].failure_time), NULL);
		lan->ip[ip_num].failure_time.tv_sec += IP_FAIL_TIME / 1000000;
		lan->ip[ip_num].failure_time.tv_usec += IP_FAIL_TIME % 1000000;
		if (lan->ip[ip_num].failure_time.tv_usec > 1000000) {
		    lan->ip[ip_num].failure_time.tv_sec += 1;
		    lan->ip[ip_num].failure_time.tv_usec -= 1000000;
		}
		lan->ip[ip_num].consecutive_failures = 1;
		ipmi_unlock(lan->ip_lock);
	    } else if (!lan->seq_table[seq].side_effects) {
		/* Don't use messages with side effects for failure
		   detection. */
		lan->ip[ip_num].consecutive_failures++;
		if (lan->ip[ip_num].consecutive_failures >= IP_FAIL_COUNT) {
		    struct timeval now;
		    ipmi_unlock(lan->ip_lock);
		    gettimeofday(&now, NULL);
		    if (cmp_timeval(&now, &lan->ip[ip_num].failure_time) > 0)
		    {
			/* Can't report yet, still holding locks. */
			call_lost_con = 1;
		    }
		} else {
		    ipmi_unlock(lan->ip_lock);
		}
	    }
	} else {
	    ipmi_unlock(lan->ip_lock);
	}
    }

    rspi = lan->seq_table[seq].rsp_item;

    if (lan->seq_table[seq].retries_left > 0)
    {
	struct timeval timeout;
	int            rv;

	lan->seq_table[seq].retries_left--;

	add_stat(ipmi, STAT_REXMITS, 1);

	/* Note that we will need a new session seq # here, we can't reuse
	   the old one.  If the message got lost on the way back, the other
	   end would silently ignore resends of the seq #. */
	if (lan->seq_table[seq].addr_num >= 0)
	    rv = lan_send_addr(lan,
			       &(lan->seq_table[seq].addr),
			       lan->seq_table[seq].addr_len,
			       &(lan->seq_table[seq].msg),
			       seq,
			       lan->seq_table[seq].addr_num,
			       NULL);
	else
	    rv = lan_send(lan,
			  &(lan->seq_table[seq].addr),
			  lan->seq_table[seq].addr_len,
			  &(lan->seq_table[seq].msg),
			  seq,
			  &(lan->seq_table[seq].last_ip_num),
			  NULL);

	if (rv) {
	    /* If we get an error resending the message, report an unknown
	       error. */
	    rspi->data[0] = IPMI_UNKNOWN_ERR_CC;
	} else {
	    if (!lan->seq_table[seq].side_effects) {
		timeout.tv_sec = LAN_RSP_TIMEOUT / 1000000;
		timeout.tv_usec = LAN_RSP_TIMEOUT % 1000000;
	    } else {
		timeout.tv_sec = LAN_RSP_TIMEOUT_SIDEEFF / 1000000;
		timeout.tv_usec = LAN_RSP_TIMEOUT_SIDEEFF % 1000000;
	    }
	    ipmi->os_hnd->start_timer(ipmi->os_hnd,
				      id,
				      &timeout,
				      rsp_timeout_handler,
				      cb_data);

	    ipmi_unlock(lan->seq_num_lock);
	    if (call_lost_con)
		lost_connection(lan, ip_num);
	    lan_put(ipmi);
	    return;
	}
    } else {
	add_stat(ipmi, STAT_TIMED_OUT, 1);

	rspi->data[0] = IPMI_TIMEOUT_CC;
    }

    rspi->msg.netfn = lan->seq_table[seq].msg.netfn | 1;
    rspi->msg.cmd = lan->seq_table[seq].msg.cmd;
    rspi->msg.data = rspi->data;
    rspi->msg.data_len = 1;

    if (lan->seq_table[seq].use_orig_addr) {
	/* We did an address translation, so translate back. */
	memcpy(&rspi->addr, &lan->seq_table[seq].orig_addr,
	       lan->seq_table[seq].orig_addr_len);
	rspi->addr_len = lan->seq_table[seq].orig_addr_len;
    } else {
	memcpy(&rspi->addr,
	       &(lan->seq_table[seq].addr),
	       lan->seq_table[seq].addr_len);
	rspi->addr_len = lan->seq_table[seq].addr_len;
    }

    handler = lan->seq_table[seq].rsp_handler;

    lan->seq_table[seq].inuse = 0;

    check_command_queue(ipmi, lan);
    ipmi_unlock(lan->seq_num_lock);

    ipmi->os_hnd->free_timer(ipmi->os_hnd, id);

    /* Convert broadcasts back into normal sends. */
    if (rspi->addr.addr_type == IPMI_IPMB_BROADCAST_ADDR_TYPE)
	rspi->addr.addr_type = IPMI_IPMB_ADDR_TYPE;

    if (call_lost_con)
	lost_connection(lan, ip_num);

    ipmi_handle_rsp_item(ipmi, rspi, handler);

 out:
    lan_put(ipmi);
    ipmi_mem_free(info);
}

typedef struct call_event_handler_s
{
    lan_data_t        *lan;
    const ipmi_addr_t *addr;
    unsigned int      addr_len;
    ipmi_event_t      *event;
} call_event_handler_t;

static int
call_event_handler(void *cb_data, void *item1, void *item2)
{
    call_event_handler_t  *info = cb_data;
    ipmi_ll_evt_handler_t handler = item1;

    handler(info->lan->ipmi, info->addr, info->addr_len, info->event, item2);
    return LOCKED_LIST_ITER_CONTINUE;
}

static int
lan_add_event_handler(ipmi_con_t            *ipmi,
		      ipmi_ll_evt_handler_t handler,
		      void                  *cb_data)
{
    lan_data_t *lan = (lan_data_t *) ipmi->con_data;

    if (locked_list_add(lan->event_handlers, handler, cb_data))
	return 0;
    else
	return ENOMEM;
}

static int
lan_remove_event_handler(ipmi_con_t            *ipmi,
			 ipmi_ll_evt_handler_t handler,
			 void                  *cb_data)
{
    lan_data_t *lan = (lan_data_t *) ipmi->con_data;

    if (locked_list_remove(lan->event_handlers, handler, cb_data))
	return 0;
    else
	return EINVAL;
}

static ipmi_mcid_t invalid_mcid = IPMI_MCID_INVALID;

static void
handle_async_event(ipmi_con_t        *ipmi,
		   const ipmi_addr_t *addr,
		   unsigned int      addr_len,
		   const ipmi_msg_t  *msg)
{
    lan_data_t           *lan = (lan_data_t *) ipmi->con_data;
    ipmi_event_t         *event = NULL;
    ipmi_time_t          timestamp;
    call_event_handler_t info;

    add_stat(ipmi, STAT_ASYNC_EVENTS, 1);

    if (msg) {
	unsigned int type = msg->data[2];
	unsigned int record_id = ipmi_get_uint16(msg->data);

	if (type < 0xe0)
	    timestamp = ipmi_seconds_to_time(ipmi_get_uint32(msg->data+3));
	else
	    timestamp = -1;
	event = ipmi_event_alloc(invalid_mcid,
				 record_id,
				 type,
				 timestamp,
				 msg->data+3, 13);
	if (!event)
	    /* We missed it here, but the SEL fetch should catch it later. */
	    return;
    }

    info.lan = lan;
    info.addr = addr;
    info.addr_len = addr_len;
    info.event = event;
    locked_list_iterate(lan->event_handlers, call_event_handler, &info);

    if (event)
	ipmi_event_free(event);
}

/* Must be called with the message sequence lock held. */
static int
handle_msg_send(lan_timer_info_t      *info,
		int                   addr_num,
		const ipmi_addr_t     *addr,
		unsigned int          addr_len,
		const ipmi_msg_t      *msg,
		ipmi_ll_rsp_handler_t rsp_handler,
		ipmi_msgi_t           *rspi,
		int                   side_effects)
{
    ipmi_con_t        *ipmi = info->ipmi;
    lan_data_t        *lan = ipmi->con_data;
    unsigned int      seq;
    struct timeval    timeout;
    int               rv;
    ipmi_addr_t       tmp_addr;
    const ipmi_addr_t *orig_addr = NULL;
    unsigned int      orig_addr_len = 0;

    seq = (lan->last_seq + 1) % 64;
    if (seq == 0)
	seq++;
    while (lan->seq_table[seq].inuse) {
	if (seq == lan->last_seq) {
	    /* This cannot really happen if max_outstanding_msg_count <= 63. */
	    ipmi_log(IPMI_LOG_FATAL,
		     "%sipmi_lan.c(handle_msg_send): "
		     "ipmi_lan: Attempted to start too many messages",
		     IPMI_CONN_NAME(ipmi));
	    abort();
	}

	seq = (seq + 1) % 64;
	if (seq == 0)
	    seq++;
    }

    if (DEBUG_MSG) {
	char buf1[32], buf2[32];
	ipmi_log(IPMI_LOG_DEBUG_START, "%soutgoing msg to IPMI addr =",
		 IPMI_CONN_NAME(ipmi));
	dump_hex((unsigned char *) addr, addr_len);
	ipmi_log(IPMI_LOG_DEBUG_CONT,
		 "\n msg  = netfn=%s cmd=%s data_len=%d",
		 ipmi_get_netfn_string(msg->netfn, buf1, 32),
		 ipmi_get_command_string(msg->netfn, msg->cmd, buf2, 32),
		 msg->data_len);
	if (msg->data_len) {
	    ipmi_log(IPMI_LOG_DEBUG_CONT, "\n data(len=%d.) =\n  ",
		     msg->data_len);
	    dump_hex(msg->data, msg->data_len);
	}
	ipmi_log(IPMI_LOG_DEBUG_END, " ");
    }

    if ((addr->addr_type == IPMI_IPMB_ADDR_TYPE)
	|| (addr->addr_type == IPMI_IPMB_BROADCAST_ADDR_TYPE))
    {
	ipmi_ipmb_addr_t *ipmb = (ipmi_ipmb_addr_t *) addr;

	if (ipmb->channel >= MAX_IPMI_USED_CHANNELS) {
	    ipmi->os_hnd->free_timer(ipmi->os_hnd, info->timer);
	    ipmi_mem_free(info);
	    rv = EINVAL;
	    goto out;
	}

	if (ipmb->slave_addr == lan->slave_addr[ipmb->channel]) {
	    ipmi_system_interface_addr_t *si = (void *) &tmp_addr;
	    /* Most systems don't handle sending to your own slave
               address, so we have to translate here. */

	    si->addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
	    si->channel = IPMI_BMC_CHANNEL;
	    si->lun = ipmb->lun;
	    orig_addr = addr;
	    orig_addr_len = addr_len;
	    addr = &tmp_addr;
	    addr_len = sizeof(*si);
	}
    }

    info->seq = seq;
    lan->seq_table[seq].inuse = 1;
    lan->seq_table[seq].side_effects = side_effects;
    lan->seq_table[seq].addr_num = addr_num;
    lan->seq_table[seq].rsp_handler = rsp_handler;
    lan->seq_table[seq].rsp_item = rspi;
    memcpy(&(lan->seq_table[seq].addr), addr, addr_len);
    lan->seq_table[seq].addr_len = addr_len;
    lan->seq_table[seq].msg = *msg;
    lan->seq_table[seq].msg.data = lan->seq_table[seq].data;
    memcpy(lan->seq_table[seq].data, msg->data, msg->data_len);
    lan->seq_table[seq].timer_info = info;
    if (addr->addr_type == IPMI_IPMB_BROADCAST_ADDR_TYPE)
	lan->seq_table[seq].retries_left = 0;
    else
	lan->seq_table[seq].retries_left = LAN_RSP_RETRIES;
    if (orig_addr) {
	lan->seq_table[seq].use_orig_addr = 1;
	memcpy(&(lan->seq_table[seq].orig_addr), orig_addr, orig_addr_len);
	lan->seq_table[seq].orig_addr_len = orig_addr_len;

	/* In case it's a broadcast. */
	lan->seq_table[seq].orig_addr.addr_type = IPMI_IPMB_ADDR_TYPE;
    } else {
	lan->seq_table[seq].use_orig_addr = 0;
    }

    if (!side_effects) {
	timeout.tv_sec = LAN_RSP_TIMEOUT / 1000000;
	timeout.tv_usec = LAN_RSP_TIMEOUT % 1000000;
    } else {
	timeout.tv_sec = LAN_RSP_TIMEOUT_SIDEEFF / 1000000;
	timeout.tv_usec = LAN_RSP_TIMEOUT_SIDEEFF % 1000000;
    }
    lan->seq_table[seq].timer = info->timer;
    rv = ipmi->os_hnd->start_timer(ipmi->os_hnd,
				   lan->seq_table[seq].timer,
				   &timeout,
				   rsp_timeout_handler,
				   info);
    if (rv) {
	lan->seq_table[seq].inuse = 0;
	ipmi->os_hnd->free_timer(ipmi->os_hnd,
				 lan->seq_table[seq].timer);
	lan->seq_table[seq].timer = NULL;
	ipmi_mem_free(info);
	goto out;
    }

    lan->last_seq = seq;

    if (addr_num >= 0) {
	rv = lan_send_addr(lan, addr, addr_len, msg, seq, addr_num, NULL);
	lan->seq_table[seq].last_ip_num = addr_num;
    } else {
	rv = lan_send(lan, addr, addr_len, msg, seq,
		      &(lan->seq_table[seq].last_ip_num),
		      NULL);
    }
    if (rv) {
	int err;

	lan->seq_table[seq].inuse = 0;
	err = ipmi->os_hnd->stop_timer(ipmi->os_hnd,
				       lan->seq_table[seq].timer);
	/* Special handling, if we can't remove the timer, then it
           will time out on us, so we need to not free the command and
           instead let the timeout handle freeing it. */
	if (err) {
	    info->cancelled = 1;
	} else {
	    ipmi->os_hnd->free_timer(ipmi->os_hnd,
				     lan->seq_table[seq].timer);
	    lan->seq_table[seq].timer = NULL;
	    ipmi_mem_free(info);
	}
    }
 out:
    return rv;
}

static void
check_command_queue(ipmi_con_t *ipmi, lan_data_t *lan)
{
    int              rv;
    lan_wait_queue_t *q_item;
    int              started = 0;

    while (!started && (lan->wait_q != NULL)) {
	/* Commands are waiting to be started, remove the queue item
           and start it. */
	q_item = lan->wait_q;
	lan->wait_q = q_item->next;
	if (lan->wait_q == NULL)
	    lan->wait_q_tail = NULL;

	rv = handle_msg_send(q_item->info, -1, &q_item->addr, q_item->addr_len,
			     &(q_item->msg), q_item->rsp_handler,
			     q_item->rsp_item, q_item->side_effects);
	if (rv) {
	    ipmi_unlock(lan->seq_num_lock);

	    /* Send an error response to the user. */
	    ipmi_log(IPMI_LOG_ERR_INFO,
		     "%sipmi_lan.c(check_command_queue): "
		     "Command was not able to be sent due to error 0x%x",
		     IPMI_CONN_NAME(ipmi), rv);
	    
	    q_item->msg.netfn |= 1; /* Convert it to a response. */
	    q_item->msg.data[0] = IPMI_UNKNOWN_ERR_CC;
	    q_item->msg.data_len = 1;
	    q_item->info = NULL;
	    ipmi_handle_rsp_item_copyall(ipmi, q_item->rsp_item,
					 &q_item->addr, q_item->addr_len,
					 &q_item->msg, q_item->rsp_handler);
	    ipmi_lock(lan->seq_num_lock);
	} else {
	    /* We successfully sent a message, break out of the loop. */
	    started = 1;
	}
	ipmi_mem_free(q_item);
    }

    if (!started)
	lan->outstanding_msg_count--;
}

/* Per the spec, RMCP and RMCP+ have different allowed sequence number
   ranges, so adjust for this. */
static int
check_session_seq_num(lan_data_t *lan, uint32_t seq,
		      uint32_t *in_seq, uint16_t *map,
		      int gt_allowed, int lt_allowed)
{
    /* Check the sequence number. */
    if ((int) (seq - *in_seq) <= gt_allowed) {
	/* It's after the current sequence number, but within 8.  We
           move the sequence number forward. */
	*map <<= seq - *in_seq;
	*map |= 1;
	*in_seq = seq;
    } else if ((int) (*in_seq - seq) <= lt_allowed) {
	/* It's before the current sequence number, but within 8. */
	uint8_t bit = 1 << (*in_seq - seq);
	if (*map & bit) {
	    /* We've already received the message, so discard it. */
	    add_stat(lan->ipmi, STAT_DUPLICATES, 1);
	    if (DEBUG_RAWMSG || DEBUG_MSG_ERR)
		ipmi_log(IPMI_LOG_DEBUG, "%sDropped message duplicate",
			 IPMI_CONN_NAME(lan->ipmi));
	    return EINVAL;
	}

	*map |= bit;
    } else {
	/* It's outside the current sequence number range, discard
	   the packet. */
	add_stat(lan->ipmi, STAT_SEQ_OUT_OF_RANGE, 1);
	if (DEBUG_RAWMSG || DEBUG_MSG_ERR)
	    ipmi_log(IPMI_LOG_DEBUG, "%sDropped message out of seq range",
		     IPMI_CONN_NAME(lan->ipmi));
	return EINVAL;
    }

    return 0;
}

static int
check_15_session_seq_num(lan_data_t *lan, uint32_t seq,
			 uint32_t *in_seq, uint16_t *map)
{
    return check_session_seq_num(lan, seq, in_seq, map, 8, 8);
}

static int
check_20_session_seq_num(lan_data_t *lan, uint32_t seq,
			 uint32_t *in_seq, uint16_t *map)
{
    return check_session_seq_num(lan, seq, in_seq, map, 15, 16);
}

static void
handle_payload(ipmi_con_t    *ipmi,
	       lan_data_t    *lan,
	       int           addr_num,
	       int           payload_type,
	       unsigned char *tmsg,
	       unsigned int  payload_len)
{
    ipmi_ll_rsp_handler_t handler;
    ipmi_msgi_t           *rspi;
    unsigned char         seq;
    int                   rv;
    int                   (*handle_send_rsp)(ipmi_con_t *con, ipmi_msg_t *msg);

    handle_send_rsp = NULL;

    if (payload_type == IPMI_RMCPP_PAYLOAD_TYPE_OPEN_SESSION_RESPONSE) {
	if (payload_len < 1) {
	    add_stat(ipmi, STAT_TOO_SHORT, 1);
	    if (DEBUG_RAWMSG || DEBUG_MSG_ERR)
		ipmi_log(IPMI_LOG_DEBUG, "%sPayload length to short",
			 IPMI_CONN_NAME(ipmi));
	    goto out;
	}

	/* We use the message tag field to store the sequence #. */
	seq = tmsg[0] & 0x3f;
    } else if (payload_type == IPMI_RMCPP_PAYLOAD_TYPE_OEM_EXPLICIT) {
#if 0
	/* FIXME - add handling of OEM payloads. */
	handle_oem_payload(ipmi, lan, oem_iana, oem_payload_id,
			   tmsg, payload_len);
#else
	goto out;
#endif
    } else if (! payloads[payload_type]) {
	add_stat(ipmi, STAT_INVALID_PAYLOAD, 1);
	if (DEBUG_RAWMSG || DEBUG_MSG_ERR)
	    ipmi_log(IPMI_LOG_DEBUG, "%sUnhandled payload: 0x%x",
		     IPMI_CONN_NAME(ipmi), payload_type);
	goto out;
    } else {
	rv = payloads[payload_type]->get_recv_seq(ipmi, tmsg,
						  payload_len, &seq);
	if (rv == ENOSYS) {
	    payloads[payload_type]->handle_recv_async(ipmi, tmsg, payload_len);
	    goto out;
	} else if (rv) {
	    add_stat(ipmi, STAT_SEQ_ERR, 1);
	    if (DEBUG_RAWMSG || DEBUG_MSG_ERR)
		ipmi_log(IPMI_LOG_DEBUG, "%sError getting sequence: 0x%x",
			 IPMI_CONN_NAME(ipmi), rv);
	    goto out;
	}
    }

    ipmi_lock(lan->seq_num_lock);
    if (! lan->seq_table[seq].inuse) {
	add_stat(ipmi, STAT_RSP_NO_CMD, 1);
	if (DEBUG_RAWMSG || DEBUG_MSG_ERR)
	    ipmi_log(IPMI_LOG_DEBUG,
		     "%sDropped message seq not in use: 0x%x",
		     IPMI_CONN_NAME(ipmi), seq);
	goto out_unlock;
    }

    rv = payloads[payload_type]->handle_recv_rsp
	(ipmi,
	 lan->seq_table[seq].rsp_item,
	 &lan->seq_table[seq].addr,
	 lan->seq_table[seq].addr_len,
	 &lan->seq_table[seq].msg,
	 tmsg,
	 payload_len);
    if (rv) {
	if (rv == -1)
	    handle_send_rsp = ipmi->handle_send_rsp_err;
	else
	    goto out_unlock;
    }

    /* We got a response from the connection, so reset the failure
       count. */
    lan->ip[addr_num].consecutive_failures = 0;

    /* The command matches up, cancel the timer and deliver it */
    rv = ipmi->os_hnd->stop_timer(ipmi->os_hnd,
				  lan->seq_table[seq].timer);
    if (rv)
	/* Couldn't cancel the timer, make sure the timer
	   doesn't do the callback. */
	lan->seq_table[seq].timer_info->cancelled = 1;
    else {
	/* Timer is cancelled, free its data. */
	ipmi->os_hnd->free_timer(ipmi->os_hnd,
				 lan->seq_table[seq].timer);
	ipmi_mem_free(lan->seq_table[seq].timer_info);
    }

    handler = lan->seq_table[seq].rsp_handler;
    rspi = lan->seq_table[seq].rsp_item;
    lan->seq_table[seq].inuse = 0;

    if (lan->seq_table[seq].use_orig_addr) {
	/* We did an address translation, so translate back. */
	memcpy(&rspi->addr, &lan->seq_table[seq].orig_addr,
	       lan->seq_table[seq].orig_addr_len);
	rspi->addr_len = lan->seq_table[seq].orig_addr_len;
    }

    check_command_queue(ipmi, lan);
    ipmi_unlock(lan->seq_num_lock);
    
    if (handle_send_rsp)
	handle_send_rsp(ipmi, &rspi->msg);

    ipmi_handle_rsp_item(ipmi, rspi, handler);

 out:
    return;

 out_unlock:
    ipmi_unlock(lan->seq_num_lock);
}

static void
handle_rmcpp_recv(ipmi_con_t    *ipmi,
		  lan_data_t    *lan,
		  int           addr_num,
		  unsigned char *data,
		  unsigned int  len)
{
    unsigned char oem_iana[3] = { 0, 0, 0 };
    unsigned int  oem_payload_id = 0;
    unsigned char *tmsg;
    int           encrypted;
    int           authenticated;
    unsigned int  payload_type;
    uint32_t      session_id;
    uint32_t      session_seq;
    int           rv;
    unsigned int  payload_len;
    unsigned int  header_len;

    if (len < 16) { /* Minimum size of an RMCP+ msg. */
	if (DEBUG_RAWMSG || DEBUG_MSG_ERR)
	    ipmi_log(IPMI_LOG_DEBUG,
		     "%sDropped message because too small(5)",
		     IPMI_CONN_NAME(ipmi));
	goto out;
    }

    encrypted = data[5] & 0x80;
    authenticated = data[5] & 0x40;
    payload_type = data[5] & 0x3f;

    tmsg = data+6;
    if (payload_type == IPMI_RMCPP_PAYLOAD_TYPE_OEM_EXPLICIT) {
	if (len < 22) { /* Minimum size of an RMCP+ type 2 msg. */
	    if (DEBUG_RAWMSG || DEBUG_MSG_ERR)
		ipmi_log(IPMI_LOG_DEBUG,
			 "%sDropped message because too small(6)",
			 IPMI_CONN_NAME(ipmi));
	    goto out;
	}
	memcpy(oem_iana, tmsg, 3);
	tmsg += 4;
	oem_payload_id = ipmi_get_uint16(tmsg);
	tmsg += 2;
    }

    session_id = ipmi_get_uint32(tmsg);
    tmsg += 4;
    if (session_id != lan->ip[addr_num].session_id) {
	add_stat(ipmi, STAT_BAD_SESSION_ID, 1);
	if (DEBUG_RAWMSG || DEBUG_MSG_ERR)
	    ipmi_log(IPMI_LOG_DEBUG,
		     "%sDropped message not valid session id (2)",
		     IPMI_CONN_NAME(ipmi));
	goto out;
    }

    session_seq = ipmi_get_uint32(tmsg);
    tmsg += 4;

    payload_len = ipmi_get_uint16(tmsg);
    tmsg += 2;

    header_len = tmsg - data;
    if ((header_len + payload_len) > len) {
	add_stat(ipmi, STAT_BAD_SIZE, 1);
	if (DEBUG_RAWMSG || DEBUG_MSG_ERR)
	    ipmi_log(IPMI_LOG_DEBUG,
		     "%sDropped message payload length doesn't match up",
		     IPMI_CONN_NAME(ipmi));
	goto out;
    }

    /* Authenticate the message before we do anything else. */
    if (authenticated) {
	unsigned int  pad_len;
	unsigned int  integ_len;

	if (lan->ip[addr_num].working_integ
	    == IPMI_LANP_INTEGRITY_ALGORITHM_NONE)
	{
	    add_stat(ipmi, STAT_INVALID_AUTH, 1);
	    if (DEBUG_RAWMSG || DEBUG_MSG_ERR)
		ipmi_log(IPMI_LOG_DEBUG,
			 "%sGot authenticated msg but authentication"
			 " not available", IPMI_CONN_NAME(ipmi));
	    goto out;
	}

	/* Increase the length to include the padding; this eases the
	   handling for the payload integrity check. */
	integ_len = header_len + payload_len;
	while ((integ_len < len) && (data[integ_len] == 0xff))
	    integ_len++;
	if (integ_len < len)
	    integ_len++;

	rv = lan->ip[addr_num].integ_info->integ_check
	    (ipmi,
	     lan->ip[addr_num].integ_data,
	     data,
	     integ_len,
	     len);
	if (rv) {
	    add_stat(ipmi, STAT_AUTH_FAIL, 1);
	    if (DEBUG_RAWMSG || DEBUG_MSG_ERR)
		ipmi_log(IPMI_LOG_DEBUG, "%sIntegrity failed",
			 IPMI_CONN_NAME(ipmi));
	    goto out;
	}

	/* Remove the integrity padding. */
	pad_len = data[integ_len-1] + 1;
	if ((integ_len - header_len - pad_len) != payload_len) {
	    add_stat(ipmi, STAT_BAD_SIZE, 1);
	    if (DEBUG_RAWMSG || DEBUG_MSG_ERR)
		ipmi_log(IPMI_LOG_DEBUG, "%sPadding size not valid: %d",
			 IPMI_CONN_NAME(ipmi), pad_len);
	    goto out;
	}
    }

    /* The packet is good, we can trust the data in it now. */

    /* If it's from a down connection, report it as up. */
    ipmi_lock(lan->ip_lock);
    if (! lan->ip[addr_num].working) {
	ipmi_unlock(lan->ip_lock);
	connection_up(lan, addr_num, 0);
	ipmi_lock(lan->ip_lock);
    }

    if (authenticated)
	rv = check_20_session_seq_num(lan, session_seq,
				      &(lan->ip[addr_num].inbound_seq_num),
				      &(lan->ip[addr_num].recv_msg_map));
    else if (session_id == 0)
	rv = 0; /* seq num not used for out-of-session messages. */
    else
	rv = check_20_session_seq_num(lan, session_seq,
				      &(lan->ip[addr_num].unauth_in_seq_num),
				      &(lan->ip[addr_num].unauth_recv_msg_map));
    ipmi_unlock(lan->ip_lock);
    if (rv) {
	if (DEBUG_RAWMSG || DEBUG_MSG_ERR)
	    ipmi_log(IPMI_LOG_DEBUG, "%sInvalid sequence number",
		     IPMI_CONN_NAME(ipmi));
	add_stat(ipmi, STAT_SEQ_OUT_OF_RANGE, 1);
	goto out;
    }

    /* Message is in sequence, so it's good to deliver after we
       decrypt it. */

    if (encrypted) {
	if (lan->ip[addr_num].working_conf
	    == IPMI_LANP_CONFIDENTIALITY_ALGORITHM_NONE)
	{
	    add_stat(ipmi, STAT_INVALID_AUTH, 1);
	    if (DEBUG_RAWMSG || DEBUG_MSG_ERR)
		ipmi_log(IPMI_LOG_DEBUG,
			 "%sGot encrypted msg but encryption not available",
			 IPMI_CONN_NAME(ipmi));
	    goto out;
	}

	rv = lan->ip[addr_num].conf_info->conf_decrypt
	    (ipmi, lan->ip[addr_num].conf_data, &tmsg, &payload_len);
	if (rv) {
	    add_stat(ipmi, STAT_DECRYPT_FAIL, 1);
	    if (DEBUG_RAWMSG || DEBUG_MSG_ERR)
		ipmi_log(IPMI_LOG_DEBUG, "%sDecryption failed",
			 IPMI_CONN_NAME(ipmi));
	    goto out;
	}
    }

    handle_payload(ipmi, lan, addr_num, payload_type, tmsg, payload_len);
    
 out:
    return;
}

static void
handle_lan15_recv(ipmi_con_t    *ipmi,
		  lan_data_t    *lan,
		  int           addr_num,
		  unsigned char *data,
		  unsigned int  len)
{
    uint32_t      seq, sess_id;
    unsigned char *tmsg = NULL;
    unsigned int  data_len;
    int           rv;

    if ((data[4] & 0x0f) == IPMI_AUTHTYPE_NONE) {
	if (len < 14) { /* Minimum size of an IPMI msg. */
	    add_stat(ipmi, STAT_TOO_SHORT, 1);
	    if (DEBUG_RAWMSG || DEBUG_MSG_ERR)
		ipmi_log(IPMI_LOG_DEBUG,
			 "%sDropped message because too small(1)",
			 IPMI_CONN_NAME(ipmi));
	    goto out;
	}

	/* No authentication. */
	if (len < (unsigned int) (data[13] + 14)) {
	    /* Not enough data was supplied, reject the message. */
	    add_stat(ipmi, STAT_TOO_SHORT, 1);
	    if (DEBUG_RAWMSG || DEBUG_MSG_ERR)
		ipmi_log(IPMI_LOG_DEBUG,
			 "%sDropped message because too small(2)",
			 IPMI_CONN_NAME(ipmi));
	    goto out;
	}
	data_len = data[13];
    } else {
	if (len < 30) { /* Minimum size of an authenticated IPMI msg. */
	    add_stat(ipmi, STAT_TOO_SHORT, 1);
	    if (DEBUG_RAWMSG || DEBUG_MSG_ERR)
		ipmi_log(IPMI_LOG_DEBUG,
			 "%sDropped message because too small(3)",
			 IPMI_CONN_NAME(ipmi));
	    goto out;
	}
	/* authcode in message, add 16 to the above checks. */
	if (len < (unsigned int) (data[29] + 30)) {
	    add_stat(ipmi, STAT_TOO_SHORT, 1);
	    /* Not enough data was supplied, reject the message. */
	    if (DEBUG_RAWMSG || DEBUG_MSG_ERR)
		ipmi_log(IPMI_LOG_DEBUG,
			 "%sDropped message because too small(4)",
			 IPMI_CONN_NAME(ipmi));
	    goto out;
	}
	data_len = data[29];
    }

    /* FIXME - need a lock on the session data. */

    /* Drop if the authtypes are incompatible. */
    if (lan->ip[addr_num].working_authtype != (data[4] & 0x0f)) {
	add_stat(ipmi, STAT_INVALID_AUTH, 1);
	if (DEBUG_RAWMSG || DEBUG_MSG_ERR)
	    ipmi_log(IPMI_LOG_DEBUG, "%sDropped message not valid authtype,"
		     " expected %d, got %d",
		     IPMI_CONN_NAME(ipmi),
		     lan->ip[addr_num].working_authtype,
		     data[4] & 0x0f);
	goto out;
    }

    /* Drop if sessions ID's don't match. */
    sess_id = ipmi_get_uint32(data+9);
    if (sess_id != lan->ip[addr_num].session_id) {
	add_stat(ipmi, STAT_BAD_SESSION_ID, 1);
	if (DEBUG_RAWMSG || DEBUG_MSG_ERR)
	    ipmi_log(IPMI_LOG_DEBUG,
		     "%sDropped message not valid session id",
		     IPMI_CONN_NAME(ipmi));
	goto out;
    }

    seq = ipmi_get_uint32(data+5);

    if ((data[4] & 0x0f) != 0) {
	/* Validate the message's authcode.  Do this before checking
           the session seq num so we know the data is valid. */
	rv = auth_check(lan, data+9, data+5, data+30, data[29], data+13,
			addr_num);
	if (rv) {
	    add_stat(ipmi, STAT_AUTH_FAIL, 1);
	    if (DEBUG_RAWMSG || DEBUG_MSG_ERR)
		ipmi_log(IPMI_LOG_DEBUG, "%sDropped message auth fail",
			 IPMI_CONN_NAME(ipmi));
	    goto out;
	}
	tmsg = data + 30;
    } else {
	tmsg = data + 14;
    }

    /* If it's from a down connection, report it as up. */
    ipmi_lock(lan->ip_lock);
    if (! lan->ip[addr_num].working) {
	ipmi_unlock(lan->ip_lock);
	connection_up(lan, addr_num, 0);
	ipmi_lock(lan->ip_lock);
    }

    rv = check_15_session_seq_num(lan, seq,
				  &(lan->ip[addr_num].inbound_seq_num),
				  &(lan->ip[addr_num].recv_msg_map));
    ipmi_unlock(lan->ip_lock);
    if (rv)
	goto out;

    /*
     * Special case for Serial-over-LAN IPMI 1.5 packets, which use the
     * "reserved" nybble to identify the SoL payload.
     */
    if ((data[4] & 0xf0) == 0x80)
	    handle_payload(ipmi, lan, addr_num,
			   IPMI_RMCPP_PAYLOAD_TYPE_SOL, tmsg, data_len);
    else
	    handle_payload(ipmi, lan, addr_num,
			   IPMI_RMCPP_PAYLOAD_TYPE_IPMI, tmsg, data_len);

 out:
    return;
}

static int
addr_match_lan(lan_data_t *lan, uint32_t sid, sockaddr_ip_t *addr,
	       int *raddr_num)
{
    unsigned int addr_num;

    /* Make sure the source address matches one we expect from
       this system. */
    for (addr_num = 0; addr_num < lan->cparm.num_ip_addr; addr_num++) {
	if ((!sid || (lan->ip[addr_num].session_id == sid))
	    && lan_addr_same(&(lan->cparm.ip_addr[addr_num]), addr))
	{
	    *raddr_num = addr_num;
	    return 1;
	}
    }
    return 0;
}

static ipmi_con_t *
rmcpp_find_ipmi(lan_fd_t      *item,
		unsigned char *data,
		unsigned int  len,
		sockaddr_ip_t *addr,
		int           *addr_num)
{
    /* This is easy, the session id is our slot in the fd or is a
       message tag. */
    unsigned char payload;
    uint32_t      tag;
    uint32_t      sid;
    unsigned char ctag;
    unsigned int  mlen;
    unsigned char *d;
    ipmi_con_t    *ipmi = NULL;
    lan_data_t    *lan;

    /* We need to find the sessions id; it's position depends on
       the payload type. */
    if (len < 16) {
	if (DEBUG_RAWMSG || DEBUG_MSG_ERR)
	    ipmi_log(IPMI_LOG_DEBUG, "Message too short(2): %d", len);
	return NULL;
    }
    payload = data[5] & 0x3f;
    if (payload == IPMI_RMCPP_PAYLOAD_TYPE_OEM_EXPLICIT) {
	if (len < 22) {
	    if (DEBUG_RAWMSG || DEBUG_MSG_ERR)
		ipmi_log(IPMI_LOG_DEBUG, "Message too short(3): %d", len);
	    return NULL;
	}
	d = data+12;
    } else {
	d = data+6;
    }

    mlen = ipmi_get_uint16(d+8);
    if ((mlen + 10 + (d-data)) > len) {
	if (DEBUG_RAWMSG || DEBUG_MSG_ERR)
	    ipmi_log(IPMI_LOG_DEBUG,
		     "Dropped message payload length doesn't match up");
	return NULL;
    }

    sid = ipmi_get_uint32(d);
    if ((sid == 0) && payloads[payload]->get_msg_tag) {
	int rv = payloads[payload]->get_msg_tag(d+10, mlen, &ctag);
	if (rv) {
	    if (DEBUG_RAWMSG || DEBUG_MSG_ERR)
		ipmi_log(IPMI_LOG_DEBUG, "Error getting message tag: %d", rv);
	    return NULL;
	}
	tag = ctag;
    } else
	tag = sid - 1;

    if (tag >= MAX_CONS_PER_FD) {
	if (DEBUG_RAWMSG || DEBUG_MSG_ERR)
	    ipmi_log(IPMI_LOG_DEBUG, "tag is out of range: %d", tag);
	return NULL;
    }

    ipmi_lock(item->con_lock);
    lan = item->lan[tag];
    if (lan && addr_match_lan(lan, sid, addr, addr_num))
	ipmi = lan->ipmi;
    else if (DEBUG_RAWMSG || DEBUG_MSG_ERR)
	ipmi_log(IPMI_LOG_DEBUG, "tag doesn't match: %d", tag);
    ipmi_unlock(item->con_lock);

    return ipmi;
}

static ipmi_con_t *
rmcp_find_ipmi(lan_fd_t      *item,
	       unsigned char *data,
	       unsigned int  len,
	       sockaddr_ip_t *addr,
	       int           *addr_num)
{
    /* Old RMCP is harder, we have to hunt. */
    uint32_t   sid;
    lan_data_t *lan;
    int        i;
    ipmi_con_t *ipmi = NULL;

    if (len < 13) {
	if (DEBUG_RAWMSG || DEBUG_MSG_ERR)
	    ipmi_log(IPMI_LOG_DEBUG, "Message too short(4): %d", len);
	return NULL;
    }

    sid = ipmi_get_uint32(data+9);
    ipmi_lock(item->con_lock);
    for (i=0; i<MAX_CONS_PER_FD; i++) {
	lan = item->lan[i];
	if (lan && addr_match_lan(lan, sid, addr, addr_num)) {
	    ipmi = lan->ipmi;
	    break;
	}
    }
    ipmi_unlock(item->con_lock);

    return ipmi;
}

static void
data_handler(int            fd,
	     void           *cb_data,
	     os_hnd_fd_id_t *id)
{
    lan_fd_t           *item = cb_data;
    ipmi_con_t         *ipmi;
    lan_data_t         *lan;
    unsigned char      data[IPMI_MAX_LAN_LEN];
    sockaddr_ip_t      ipaddrd;
    socklen_t          from_len;
    int                len;
    int                addr_num = 0; /* Keep gcc happy and initialize */

    from_len = sizeof(ipaddrd.s_ipsock);
    len = recvfrom(fd, data, sizeof(data), 0, (struct sockaddr *)&ipaddrd, 
		   &from_len);

    if (len < 0)
	/* Got an error, probably no data, just return. */
	return;

    ipaddrd.ip_addr_len = from_len;
    if (DEBUG_RAWMSG) {
	ipmi_log(IPMI_LOG_DEBUG_START, "incoming\n addr = ");
	dump_hex((unsigned char *) &ipaddrd, from_len);
	if (len) {
	    ipmi_log(IPMI_LOG_DEBUG_CONT, "\n data =\n  ");
	    dump_hex(data, len);
	}
	ipmi_log(IPMI_LOG_DEBUG_END, " ");
    }

    if (len < 5) {
	if (DEBUG_RAWMSG || DEBUG_MSG_ERR)
	    ipmi_log(IPMI_LOG_DEBUG, "Message too short(1): %d", len);
	return;
    }

    /* Validate the RMCP portion of the message. */
    if ((data[0] != 6)
	|| (data[2] != 0xff)
	|| (data[3] != 0x07))
    {
	if (DEBUG_RAWMSG || DEBUG_MSG_ERR)
	    ipmi_log(IPMI_LOG_DEBUG, "Dropped message not valid IPMI/RMCP");
	return;
    }

    if ((data[4] & 0x0f) == IPMI_AUTHTYPE_RMCP_PLUS) {
	ipmi = rmcpp_find_ipmi(item, data, len, &ipaddrd, &addr_num);
    } else {
	ipmi = rmcp_find_ipmi(item, data, len, &ipaddrd, &addr_num);
    }

    if (!lan_valid_ipmi(ipmi))
	/* This can fail due to a race condition, just return and
           everything should be fine. */
	return;

    lan = ipmi->con_data;

    add_stat(ipmi, STAT_RECV_PACKETS, 1);

    if ((data[4] & 0x0f) == IPMI_AUTHTYPE_RMCP_PLUS) {
	handle_rmcpp_recv(ipmi, lan, addr_num, data, len);
    } else {
	handle_lan15_recv(ipmi, lan, addr_num, data, len);
    }
    
    lan_put(ipmi);
    return;
}

/* Note that this puts the address number in data4 of the rspi. */
int
ipmi_lan_send_command_forceip(ipmi_con_t            *ipmi,
			      int                   addr_num,
			      ipmi_addr_t           *addr,
			      unsigned int          addr_len,
			      ipmi_msg_t            *msg,
			      ipmi_ll_rsp_handler_t rsp_handler,
			      ipmi_msgi_t           *rspi)
{
    lan_timer_info_t *info;
    lan_data_t       *lan;
    int              rv;
    /* We store the address number in data4. */

    if (addr_num >= MAX_IP_ADDR)
	return EINVAL;

    if (addr_len > sizeof(ipmi_addr_t))
	return EINVAL;

    if (msg->data_len > IPMI_MAX_MSG_LENGTH)
	return EINVAL;

    lan = (lan_data_t *) ipmi->con_data;

    if (lan->in_cleanup)
	return ECANCELED;

    /* Odd netfns are responses or unacknowledged data.  Just send
       them. */
    if (msg->netfn & 1)
	return lan_send_addr(lan, addr, addr_len, msg, 0, addr_num, NULL);

    info = ipmi_mem_alloc(sizeof(*info));
    if (!info)
	return ENOMEM;
    memset(info, 0, sizeof(*info));

    /* Put it in the list first. */
    info->ipmi = ipmi;
    info->cancelled = 0;

    rv = ipmi->os_hnd->alloc_timer(ipmi->os_hnd, &(info->timer));
    if (rv) {
	ipmi_mem_free(info);
	return rv;
    }

    ipmi_lock(lan->seq_num_lock);

    if (lan->outstanding_msg_count >= 60) {
	rv = EAGAIN;
	goto out_unlock;
    }

    rspi->data4 = (void *) (long) addr_num;
    rv = handle_msg_send(info, addr_num, addr, addr_len, msg,
			 rsp_handler, rspi, 0);
    /* handle_msg_send handles freeing the timer and info on an error */
    info = NULL;
    if (! rv)
	lan->outstanding_msg_count++;
    ipmi_unlock(lan->seq_num_lock);
    return rv;

 out_unlock:
    ipmi_unlock(lan->seq_num_lock);
    if (rv) {
	if (info) {
	    if (info->timer)
		ipmi->os_hnd->free_timer(ipmi->os_hnd, info->timer);
	    ipmi_mem_free(info);
	}
    }
    return rv;
}

static int
lan_send_command_option(ipmi_con_t              *ipmi,
			const ipmi_addr_t       *addr,
			unsigned int            addr_len,
			const ipmi_msg_t        *msg,
			const ipmi_con_option_t *options,
			ipmi_ll_rsp_handler_t   rsp_handler,
			ipmi_msgi_t             *trspi)
{
    lan_timer_info_t *info;
    lan_data_t       *lan;
    int              rv;
    ipmi_msgi_t      *rspi = trspi;
    int              side_effects = 0;
    int              i;


    if (addr_len > sizeof(ipmi_addr_t))
	return EINVAL;

    if (msg->data_len > IPMI_MAX_MSG_LENGTH)
	return EINVAL;

    lan = (lan_data_t *) ipmi->con_data;

    /* Odd netfns are responses or unacknowledged data.  Just send
       them. */
    if (msg->netfn & 1) {
	int dummy_send_ip;
	return lan_send(lan, addr, addr_len, msg, 0, &dummy_send_ip, options);
    }

    if (options) {
	for (i=0; options[i].option != IPMI_CON_OPTION_LIST_END; i++) {
	    if (options[i].option == IPMI_CON_MSG_OPTION_SIDE_EFFECTS)
		side_effects = options[i].ival;
	}
    }

    if (!rspi) {
	rspi = ipmi_mem_alloc(sizeof(*rspi));
	if (!rspi)
	    return ENOMEM;
    }

    info = ipmi_mem_alloc(sizeof(*info));
    if (!info) {
	rv = ENOMEM;
	goto out_unlock2;
    }
    memset(info, 0, sizeof(*info));

    /* Put it in the list first. */
    info->ipmi = ipmi;
    info->cancelled = 0;

    rv = ipmi->os_hnd->alloc_timer(ipmi->os_hnd, &(info->timer));
    if (rv)
	goto out_unlock;

    ipmi_lock(lan->seq_num_lock);

    if (lan->outstanding_msg_count >= lan->max_outstanding_msg_count) {
	lan_wait_queue_t *q_item;

	q_item = ipmi_mem_alloc(sizeof(*q_item));
	if (!q_item) {
	    ipmi->os_hnd->free_timer(ipmi->os_hnd, info->timer);
	    rv = ENOMEM;
	    goto out_unlock;
	}

	q_item->info = info;
	memcpy(&(q_item->addr), addr, addr_len);
	q_item->addr_len = addr_len;
	memcpy(&q_item->msg, msg, sizeof(q_item->msg));
	q_item->msg.data = q_item->data;
	memcpy(q_item->data, msg->data, msg->data_len);
	q_item->rsp_handler = rsp_handler;
	q_item->rsp_item = rspi;
	q_item->side_effects = side_effects;

	/* Add it to the end of the queue. */
	q_item->next = NULL;
	if (lan->wait_q_tail == NULL) {
	    lan->wait_q_tail = q_item;
	    lan->wait_q = q_item;
	} else {
	    lan->wait_q_tail->next = q_item;
	    lan->wait_q_tail = q_item;
	}
	goto out_unlock;
    }

    rv = handle_msg_send(info, -1, addr, addr_len, msg,
			 rsp_handler, rspi, side_effects);
    /* handle_msg_send handles freeing the timer and info on an error */
    info = NULL;
    if (!rv)
	lan->outstanding_msg_count++;
    else if (!trspi && rspi)
	/* If we allocated an rspi, free it on error. */
	ipmi_mem_free(rspi);
    ipmi_unlock(lan->seq_num_lock);
    return rv;

 out_unlock:
    ipmi_unlock(lan->seq_num_lock);
    if (rv) {
	if (info) {
	    if (info->timer)
		ipmi->os_hnd->free_timer(ipmi->os_hnd, info->timer);
	    ipmi_mem_free(info);
	}
    }
 out_unlock2:
    if (rv) {
	/* If we allocated an rspi, free it. */
	if (!trspi && rspi)
	    ipmi_mem_free(rspi);
    }
    return rv;
}

static int
lan_send_command(ipmi_con_t            *ipmi,
		 const ipmi_addr_t     *addr,
		 unsigned int          addr_len,
		 const ipmi_msg_t      *msg,
		 ipmi_ll_rsp_handler_t rsp_handler,
		 ipmi_msgi_t           *trspi)
{
    return lan_send_command_option(ipmi, addr, addr_len, msg, NULL,
				   rsp_handler, trspi);
}

static int
lan_send_response(ipmi_con_t        *ipmi,
		  const ipmi_addr_t *addr,
		  unsigned int      addr_len,
		  const ipmi_msg_t  *msg,
		  long              sequence)
{
    return ENOSYS;
}

static int
lan_register_for_command(ipmi_con_t            *ipmi,
			 unsigned char         netfn,
			 unsigned char         cmd,
			 ipmi_ll_cmd_handler_t handler,
			 void                  *cmd_data,
			 void                  *data2,
			 void                  *data3)
{
    return ENOSYS;
}

static int
lan_deregister_for_command(ipmi_con_t    *ipmi,
			   unsigned char netfn,
			   unsigned char cmd)
{
    return ENOSYS;
}

static unsigned int
lan_get_num_ports(ipmi_con_t *ipmi)
{
    lan_data_t *lan = (lan_data_t *) ipmi->con_data;

    return lan->cparm.num_ip_addr;
}

static int
lan_get_port_info(ipmi_con_t *ipmi, unsigned int port,
		  char *info, int *info_len)
{
    lan_data_t    *lan = (lan_data_t *) ipmi->con_data;
    sockaddr_ip_t *a;
    int           count = 0;
    int           len = *info_len;

    if (port > lan->cparm.num_ip_addr)
	return EINVAL;

    a = &(lan->cparm.ip_addr[port]);

    if (lan->ip[port].working_authtype == IPMI_AUTHTYPE_RMCP_PLUS)
	count = snprintf(info, len, "rmcp+: ");
    else
	count = snprintf(info, len, "rmcp: ");
    
    switch (a->s_ipsock.s_addr.sa_family) {
    case PF_INET:
	{
	    struct sockaddr_in *ip = &a->s_ipsock.s_addr4;
	    char buf[INET_ADDRSTRLEN];

	    inet_ntop(AF_INET, &ip->sin_addr, buf, sizeof(buf));
	    count += snprintf(info+count, len-count, "inet:%s:%d",
			      buf, ntohs(ip->sin_port));
	}
	break;

#ifdef PF_INET6
    case PF_INET6:
	{
	    struct sockaddr_in6 *ip = &a->s_ipsock.s_addr6;
	    char buf[INET6_ADDRSTRLEN];

	    inet_ntop(AF_INET6, &ip->sin6_addr, buf, sizeof(buf));
	    count += snprintf(info+count, len-count, "inet6:%s:%d",
			      buf, ntohs(ip->sin6_port));
	}
	break;
#endif
    default:
	count += snprintf(info+count, len-count, "invalid");
	break;
    }

    *info_len = count;
    return 0;
}

static void *
auth_alloc(void *info, int size)
{
    return ipmi_mem_alloc(size);
}

static void
auth_free(void *info, void *data)
{
    ipmi_mem_free(data);
}

/* Send the final close session to shut the connection down. */
static void
send_close_session(ipmi_con_t *ipmi, lan_data_t *lan, int addr_num)
{
    ipmi_msg_t                   msg;
    unsigned char                data[4];
    ipmi_system_interface_addr_t si;

    si.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
    si.channel = 0xf;
    si.lun = 0;
    msg.netfn = IPMI_APP_NETFN;
    msg.cmd = IPMI_CLOSE_SESSION_CMD;
    msg.data_len = 4;
    msg.data = data;
    if (lan->ip[addr_num].working_authtype == IPMI_AUTHTYPE_RMCP_PLUS)
	ipmi_set_uint32(data, lan->ip[addr_num].mgsys_session_id);
    else
	ipmi_set_uint32(data, lan->ip[addr_num].session_id);
    lan_send_addr(lan, (ipmi_addr_t *) &si, sizeof(si), &msg, 0, addr_num,
		  NULL);
}

typedef struct lan_unreg_stat_info_s
{
    lan_data_t          *lan;
    ipmi_ll_stat_info_t *cmpinfo;
    int                 found;
} lan_unreg_stat_info_t;

static int
lan_unreg_stat_info(void *cb_data, void *item1, void *item2)
{
    ipmi_ll_stat_info_t   *info = item2;
    lan_stat_info_t       *stat = item1;
    lan_unreg_stat_info_t *sinfo = cb_data;
    int                   i;

    if (!sinfo->cmpinfo || (sinfo->cmpinfo == info)) {
	locked_list_remove(sinfo->lan->lan_stat_list, stat, info);
	for (i=0; i<NUM_STATS; i++)
	    if (stat->stats[i]) {
		ipmi_ll_con_stat_call_unregister(info, stat->stats[i]);
		stat->stats[i] = NULL;
	    }
	ipmi_mem_free(stat);
	sinfo->found = 1;
    }
    return LOCKED_LIST_ITER_CONTINUE;
}

static void
cleanup_con(ipmi_con_t *ipmi)
{
    lan_data_t   *lan = NULL;
    unsigned int i;

    if (ipmi) {
	lan = (lan_data_t *) ipmi->con_data;
	ipmi_con_attr_cleanup(ipmi);
	if (ipmi->name) {
	    ipmi_mem_free(ipmi->name);
	    ipmi->name = NULL;
	}
	ipmi_mem_free(ipmi);
    }

    if (lan) {
	/* This is only called in the case of an error at startup, so
	   there is no need to remove it from the LAN lists (hashes),
	   because it won't be there yet. */

	for (i=0; i<lan->cparm.num_ip_addr; i++) {
	    if (lan->cparm.ip_addr_str[i])
		ipmi_mem_free(lan->cparm.ip_addr_str[i]);
	    if (lan->cparm.ip_port_str[i])
		ipmi_mem_free(lan->cparm.ip_port_str[i]);
	}

	if (lan->lan_stat_list) {
	    lan_unreg_stat_info_t sinfo;
	    sinfo.lan = lan;
	    sinfo.cmpinfo = NULL;
	    sinfo.found = 0;
	    locked_list_iterate(lan->lan_stat_list, lan_unreg_stat_info,
				&sinfo);
	    locked_list_destroy(lan->lan_stat_list);
	}
	if (lan->con_change_lock)
	    ipmi_destroy_lock(lan->con_change_lock);
	if (lan->ip_lock)
	    ipmi_destroy_lock(lan->ip_lock);
	if (lan->con_change_handlers)
	    locked_list_destroy(lan->con_change_handlers);
	if (lan->event_handlers)
	    locked_list_destroy(lan->event_handlers);
	if (lan->ipmb_change_handlers)
	    locked_list_destroy(lan->ipmb_change_handlers);
	if (lan->seq_num_lock)
	    ipmi_destroy_lock(lan->seq_num_lock);
	if (lan->fd)
	    release_lan_fd(lan->fd, lan->fd_slot);
	if (lan->authdata)
	    ipmi_auths[lan->chosen_authtype].authcode_cleanup(lan->authdata);
	for (i=0; i<MAX_IP_ADDR; i++) {
	    if (lan->ip[i].conf_data)
		lan->ip[i].conf_info->conf_free(ipmi, lan->ip[i].conf_data);
	    if (lan->ip[i].integ_data)
		lan->ip[i].integ_info->integ_free(ipmi, lan->ip[i].integ_data);
	}
	/* paranoia */
	memset(lan->cparm.password, 0, sizeof(lan->cparm.password));
	memset(lan->cparm.bmc_key, 0, sizeof(lan->cparm.bmc_key));
	ipmi_mem_free(lan);
    }
}

static void
lan_cleanup(ipmi_con_t *ipmi)
{
    lan_data_t   *lan = ipmi->con_data;
    int          rv;
    unsigned int i;

    /* After this point no other operations can occur on this ipmi
       interface, so it's safe. */

    for (i=0; i<lan->cparm.num_ip_addr; i++)
	send_close_session(ipmi, lan, i);

    lan->in_cleanup = 1;

    ipmi_lock(lan->seq_num_lock);
    for (i=0; i<64; i++) {
	if (lan->seq_table[i].inuse) {
	    ipmi_ll_rsp_handler_t handler;
	    ipmi_msgi_t           *rspi;
	    lan_timer_info_t      *info;

	    rv = ipmi->os_hnd->stop_timer(ipmi->os_hnd,
					  lan->seq_table[i].timer);

	    rspi = lan->seq_table[i].rsp_item;

	    if (lan->seq_table[i].use_orig_addr) {
		/* We did an address translation, so translate back. */
		memcpy(&rspi->addr, &lan->seq_table[i].orig_addr,
		       lan->seq_table[i].orig_addr_len);
		rspi->addr_len = lan->seq_table[i].orig_addr_len;
	    } else {
		memcpy(&rspi->addr, &(lan->seq_table[i].addr),
		       lan->seq_table[i].addr_len);
		rspi->addr_len = lan->seq_table[i].addr_len;
	    }
	    handler = lan->seq_table[i].rsp_handler;
	    info = lan->seq_table[i].timer_info;

	    rspi->msg.netfn = lan->seq_table[i].msg.netfn | 1;
	    rspi->msg.cmd = lan->seq_table[i].msg.cmd;
	    rspi->msg.data = rspi->data;
	    rspi->data[0] = IPMI_UNKNOWN_ERR_CC;
	    rspi->msg.data_len = 1;

	    lan->seq_table[i].inuse = 0;

	    /* Wait until here to free the info, as we use it above.
	       But we must be holding the lock while we do this. */
	    if (rv)
		info->cancelled = 1;
	    else {
		ipmi->os_hnd->free_timer(ipmi->os_hnd, info->timer);
		ipmi_mem_free(info);
	    }

	    ipmi_unlock(lan->seq_num_lock);

	    /* The unlock is safe here because the connection is no
               longer valid and thus nothing else can really happen on
               this connection.  Sends will fail and receives will not
               validate. */
	    
	    ipmi_handle_rsp_item(NULL, rspi, handler);

	    ipmi_lock(lan->seq_num_lock);
	}
    }
    while (lan->wait_q != NULL) {
	lan_wait_queue_t *q_item;

	q_item = lan->wait_q;
	lan->wait_q = q_item->next;

	ipmi->os_hnd->free_timer(ipmi->os_hnd, q_item->info->timer);

	ipmi_unlock(lan->seq_num_lock);

	q_item->msg.netfn |= 1; /* Convert it to a response. */
	q_item->msg.data[0] = IPMI_UNKNOWN_ERR_CC;
	q_item->msg.data_len = 1;
	ipmi_handle_rsp_item_copyall(ipmi, q_item->rsp_item,
				     &q_item->addr, q_item->addr_len,
				     &q_item->msg, q_item->rsp_handler);

	ipmi_lock(lan->seq_num_lock);

	ipmi_mem_free(q_item->info);
	ipmi_mem_free(q_item);
    }
    if (lan->audit_info) {
	rv = ipmi->os_hnd->stop_timer(ipmi->os_hnd, lan->audit_timer);
	if (rv)
	    lan->audit_info->cancelled = 1;
	else {
	    ipmi->os_hnd->free_timer(ipmi->os_hnd, lan->audit_timer);
	    ipmi_mem_free(lan->audit_info);
	}
    }
    ipmi_unlock(lan->seq_num_lock);

    if (lan->close_done)
	lan->close_done(ipmi, lan->close_cb_data);

    if (ipmi->oem_data_cleanup)
	ipmi->oem_data_cleanup(ipmi);

    cleanup_con(ipmi);
}

static int
lan_close_connection_done(ipmi_con_t            *ipmi,
			  ipmi_ll_con_closed_cb handler,
			  void                  *cb_data)
{
    lan_data_t *lan;

    if (! lan_valid_ipmi(ipmi))
	return EINVAL;

    lan = (lan_data_t *) ipmi->con_data;

    ipmi_lock(lan_list_lock);
    if (lan->users > 1) {
	/* The connection has been reused, just report it going
	   down. */
	lan->users--;
	ipmi_unlock(lan_list_lock);
	if (handler)
	    handler(ipmi, cb_data);
	lan_put(ipmi);
	return 0;
    }

    /* Once we begin the shutdown process, we don't want anyone else
       reusing the connection. */
    lan_remove_con_nolock(lan);
    ipmi_unlock(lan_list_lock);

    lan->close_done = handler;
    lan->close_cb_data = cb_data;

    /* Put it once for the lan_valid_ipmi() call, then once to
       actually destroy it. */
    lan_put(ipmi);
    lan_put(ipmi);
    return 0;
}

static int
lan_close_connection(ipmi_con_t *ipmi)
{
    return lan_close_connection_done(ipmi, NULL, NULL);
}

static void
handle_connected(ipmi_con_t *ipmi, int err, int addr_num)
{
    lan_data_t *lan;

    if (!ipmi)
	return;

    lan = (lan_data_t *) ipmi->con_data;

    /* This should be occurring single-threaded (the IP is down and is
       being brought back up or is initially coming up), so no need
       for a lock here. */

    /* Make sure session data is reset on an error. */
    if (err)
	reset_session_data(lan, addr_num);

    ipmi_lock(lan->ip_lock);
    ipmi_lock(lan->con_change_lock);
    ipmi_unlock(lan->ip_lock);
    call_con_change_handlers(lan, err, addr_num, lan->connected);
    ipmi_unlock(lan->con_change_lock);
}

static void
finish_connection(ipmi_con_t *ipmi, lan_data_t *lan, int addr_num)
{
    lan->connected = 1;
    connection_up(lan, addr_num, 1);
    if (! lan->initialized) {
	lan->initialized = 1;
	handle_connected(ipmi, 0, addr_num);
    }
}

static void
lan_set_ipmb_addr(ipmi_con_t    *ipmi,
		  const unsigned char ipmb_addr[],
		  unsigned int  num_ipmb_addr,
		  int           active,
		  unsigned int  hacks)
{
    lan_data_t   *lan = (lan_data_t *) ipmi->con_data;
    int          changed = 0;
    unsigned int i;

    for (i=0; i<num_ipmb_addr && i<MAX_IPMI_USED_CHANNELS; i++) {
	if (! ipmb_addr[i])
	    continue;
	if (lan->slave_addr[i] != ipmb_addr[i]) {
	    lan->slave_addr[i] = ipmb_addr[i];
	    ipmi->ipmb_addr[i] = ipmb_addr[i];
	    changed = 1;
	}
    }

    if (changed || (lan->is_active != active))  {
	lan->is_active = active;
	ipmi->hacks = hacks;
	call_ipmb_change_handlers(lan, 0, ipmb_addr, num_ipmb_addr,
				  active, hacks);
    }
}

static void
handle_ipmb_addr(ipmi_con_t   *ipmi,
		 int          err,
		 const unsigned char ipmb_addr[],
		 unsigned int  num_ipmb_addr,
		 int          active,
		 unsigned int hacks,
		 void         *cb_data)
{
    lan_data_t   *lan;
    unsigned int addr_num = (unsigned long) cb_data;
    unsigned int i;

    if (err) {
	handle_connected(ipmi, err, addr_num);
	return;
    }

    if (!ipmi) {
	handle_connected(ipmi, ECANCELED, addr_num);
	return;
    }

    lan = (lan_data_t *) ipmi->con_data;

    for (i=0; i<num_ipmb_addr && i<MAX_IPMI_USED_CHANNELS; i++) {
	if (! ipmb_addr[i])
	    continue;
	lan->slave_addr[i] = ipmb_addr[i];
	ipmi->ipmb_addr[i] = ipmb_addr[i];
    }

    lan->is_active = active;
    ipmi->hacks = hacks;
    finish_connection(ipmi, lan, addr_num);
    call_ipmb_change_handlers(lan, err, ipmb_addr, num_ipmb_addr,
			      active, hacks);
}

static int
handle_dev_id(ipmi_con_t *ipmi, ipmi_msgi_t *rspi)
{
    ipmi_msg_t   *msg = &rspi->msg;
    lan_data_t   *lan = NULL;
    int          err;
    unsigned int manufacturer_id;
    unsigned int product_id;
    int          addr_num = (long) rspi->data4;

    if (!ipmi) {
	err = ECANCELED;
	goto out_err;
    }

    lan = (lan_data_t *) ipmi->con_data;

    if (msg->data[0] != 0) {
	err = IPMI_IPMI_ERR_VAL(msg->data[0]);
	goto out_err;
    }

    if (msg->data_len < 12) {
	err = EINVAL;
	goto out_err;
    }

    manufacturer_id = (msg->data[7]
		       | (msg->data[8] << 8)
		       | (msg->data[9] << 16));
    product_id = msg->data[10] | (msg->data[11] << 8);

    if (!lan->oem_conn_handlers_called) {
	lan->oem_conn_handlers_called = 1;
	err = ipmi_check_oem_conn_handlers(ipmi, manufacturer_id, product_id);
	if (err)
	    goto out_err;

	if (ipmi->get_ipmb_addr) {
	    /* We have a way to fetch the IPMB address, do so. */
	    err = ipmi->get_ipmb_addr(ipmi, handle_ipmb_addr,
				      (void *) (long) addr_num);
	    if (err)
		goto out_err;
	} else
	    finish_connection(ipmi, lan, addr_num);
    } else {
	finish_connection(ipmi, lan, addr_num);
    }
    return IPMI_MSG_ITEM_NOT_USED;

 out_err:
    handle_connected(ipmi, err, addr_num);
    return IPMI_MSG_ITEM_NOT_USED;
}

static int
send_get_dev_id(ipmi_con_t *ipmi, lan_data_t *lan, int addr_num,
		ipmi_msgi_t *rspi)
{
    ipmi_msg_t			 msg;
    int				 rv;
    ipmi_system_interface_addr_t addr;

    addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
    addr.channel = 0xf;
    addr.lun = 0;

    msg.cmd = IPMI_GET_DEVICE_ID_CMD;
    msg.netfn = IPMI_APP_NETFN;
    msg.data = NULL;
    msg.data_len = 0;

    rv = ipmi_lan_send_command_forceip(ipmi, addr_num,
				       (ipmi_addr_t *) &addr, sizeof(addr),
				       &msg, handle_dev_id, rspi);
    return rv;
}

static void
lan_oem_done(ipmi_con_t *ipmi, void *cb_data)
{
    lan_data_t  *lan;
    int         rv;
    ipmi_msgi_t *rspi = cb_data;
    int         addr_num = (long) rspi->data4;

    if (! ipmi) {
	ipmi_mem_free(rspi);
	return;
    }

    lan = (lan_data_t *) ipmi->con_data;
    rv = send_get_dev_id(ipmi, lan, addr_num, rspi);
    if (rv) {
        handle_connected(ipmi, rv, addr_num);
	ipmi_mem_free(rspi);
    }
}

static int
session_privilege_set(ipmi_con_t *ipmi, ipmi_msgi_t *rspi)
{
    ipmi_msg_t *msg = &rspi->msg;
    lan_data_t *lan;
    int        rv;
    int        addr_num = (long) rspi->data4;

    if (!ipmi) {
	handle_connected(ipmi, ECANCELED, addr_num);
	goto out;
    }

    lan = (lan_data_t *) ipmi->con_data;

    if (msg->data[0] != 0) {
        handle_connected(ipmi, IPMI_IPMI_ERR_VAL(msg->data[0]), addr_num);
	goto out;
    }

    if (msg->data_len < 2) {
        handle_connected(ipmi, EINVAL, addr_num);
	goto out;
    }

    if (lan->cparm.privilege != (unsigned int) (msg->data[1] & 0xf)) {
	/* Requested privilege level did not match. */
        handle_connected(ipmi, EINVAL, addr_num);
	goto out;
    }

    rv = ipmi_conn_check_oem_handlers(ipmi, lan_oem_done, rspi);
    if (rv) {
        handle_connected(ipmi, rv, addr_num);
	goto out;
    }

    return IPMI_MSG_ITEM_USED;

 out:
    return IPMI_MSG_ITEM_NOT_USED;
}

static int
send_set_session_privilege(ipmi_con_t *ipmi, lan_data_t *lan, int addr_num,
			   ipmi_msgi_t *rspi)
{
    unsigned char		 data[1];
    ipmi_msg_t			 msg;
    int				 rv;
    ipmi_system_interface_addr_t addr;

    addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
    addr.channel = 0xf;
    addr.lun = 0;

    data[0] = lan->cparm.privilege;

    msg.cmd = IPMI_SET_SESSION_PRIVILEGE_CMD;
    msg.netfn = IPMI_APP_NETFN;
    msg.data = data;
    msg.data_len = 1;

    rv = ipmi_lan_send_command_forceip(ipmi, addr_num,
				       (ipmi_addr_t *) &addr, sizeof(addr),
				       &msg, session_privilege_set, rspi);
    return rv;
}

static int
check_rakp_rsp(ipmi_con_t   *ipmi,
	       ipmi_msg_t   *msg,
	       char         *caller,
	       unsigned int min_length,
	       int          addr_num)
{
    if (!ipmi) {
	handle_connected(ipmi, ECANCELED, addr_num);
	return ECANCELED;
    }

    if (msg->data_len < 2) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%sipmi_lan.c(%s): Message data too short: %d",
		 IPMI_CONN_NAME(ipmi), caller, msg->data_len);
	handle_connected(ipmi, EINVAL, addr_num);
	return EINVAL;
    }

    if (msg->data[1]) {
	/* Got an RMCP+ error. */
	handle_connected(ipmi, IPMI_RMCPP_ERR_VAL(msg->data[1]), addr_num);
	return EINVAL;
    }

    if (msg->data_len < min_length) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%sipmi_lan.c(%s): Message data too short: %d",
		 IPMI_CONN_NAME(ipmi), caller, msg->data_len);
	handle_connected(ipmi, EINVAL, addr_num);
	return EINVAL;
    }

    return 0;
}

typedef struct auth_info_s
{
    ipmi_msgi_t *rspi;
    lan_data_t  *lan;
} auth_info_t;

static void
rmcpp_auth_finished(ipmi_con_t    *ipmi,
		    int           err,
		    int           addr_num,
		    void          *cb_data)
{
    auth_info_t *info = cb_data;
    lan_data_t  *lan = info->lan;
    int         rv = EINVAL;

    if (!ipmi) {
	handle_connected(lan->ipmi, ECANCELED, addr_num);
	goto out;
    }

    if (err) {
	handle_connected(lan->ipmi, err, addr_num);
	goto out;
    }

    lan->ip[addr_num].session_id = lan->ip[addr_num].precon_session_id;
    lan->ip[addr_num].mgsys_session_id
	= lan->ip[addr_num].precon_mgsys_session_id;
    lan->ip[addr_num].inbound_seq_num = 1;
    lan->ip[addr_num].outbound_seq_num = 1;
    lan->ip[addr_num].unauth_in_seq_num = 1;
    lan->ip[addr_num].unauth_out_seq_num = 1;

    /* We're up!.  Start the session stuff. */
    rv = send_set_session_privilege(ipmi, lan, addr_num, info->rspi);
    if (rv) {
        handle_connected(ipmi, rv, addr_num);
	goto out;
    }

 out:
    if (rv)
	ipmi_free_msg_item(info->rspi);
    ipmi_mem_free(info);
    return;
}

static int
rmcpp_set_info(ipmi_con_t        *ipmi,
	       int               addr_num,
	       ipmi_rmcpp_auth_t *ainfo,
	       void              *cb_data)
{
    auth_info_t *info = cb_data;
    lan_data_t  *lan = info->lan;
    int         rv;

    rv = lan->ip[addr_num].conf_info->conf_init
	(ipmi, ainfo, &(lan->ip[addr_num].conf_data));
    if (rv)
	goto out;

    rv = lan->ip[addr_num].integ_info->integ_init
	(ipmi, ainfo, &(lan->ip[addr_num].integ_data));
    if (rv)
	goto out;

 out:
    return rv;
}

static int
got_rmcpp_open_session_rsp(ipmi_con_t *ipmi, ipmi_msgi_t  *rspi)
{
    ipmi_msg_t   *msg = &rspi->msg;
    lan_data_t   *lan;
    int          addr_num = (long) rspi->data4;
    uint32_t     session_id;
    uint32_t     mgsys_session_id;
    unsigned int privilege;
    unsigned int auth, integ, conf;
    ipmi_rmcpp_authentication_t *authp = NULL;
    ipmi_rmcpp_confidentiality_t *confp = NULL;
    ipmi_rmcpp_integrity_t *integp = NULL;
    auth_info_t  *info;
    int          rv;

    if (check_rakp_rsp(ipmi, msg, "got_rmcpp_open_session_rsp", 36, addr_num))
	goto out;

    lan = (lan_data_t *) ipmi->con_data;

    privilege = msg->data[2] & 0xf;
    if (privilege != lan->cparm.privilege) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%sipmi_lan.c(got_rmcpp_open_session_rsp): "
		 "Expected privilege %d, got %d",
		 IPMI_CONN_NAME(ipmi), lan->cparm.privilege, privilege);
	handle_connected(ipmi, EINVAL, addr_num);
	goto out;
    }

    session_id = ipmi_get_uint32(msg->data+4);
    if (session_id != lan->ip[addr_num].precon_session_id) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%sipmi_lan.c(got_rmcpp_open_session_rsp): "
		 " Got wrong session id: 0x%x",
		 IPMI_CONN_NAME(ipmi), session_id);
	handle_connected(ipmi, EINVAL, addr_num);
	goto out;
    }

    mgsys_session_id = ipmi_get_uint32(msg->data+8);
    if (mgsys_session_id == 0) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%sipmi_lan.c(got_rmcpp_open_session_rsp): "
		 "Got NULL mgd system session id", IPMI_CONN_NAME(ipmi));
	handle_connected(ipmi, EINVAL, addr_num);
	goto out;
    }
    lan->ip[addr_num].precon_mgsys_session_id = mgsys_session_id;

    if ((msg->data[12] != 0) || (msg->data[15] != 8)) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%sipmi_lan.c(got_rmcpp_open_session_rsp): "
		 "Got NULL or invalid authentication payload",
		 IPMI_CONN_NAME(ipmi));
	handle_connected(ipmi, EINVAL, addr_num);
	goto out;
    }
    auth = msg->data[16] & 0x3f;

    if ((msg->data[20] != 1) || (msg->data[23] != 8)) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%sipmi_lan.c(got_rmcpp_open_session_rsp): "
		 "Got NULL or invalid integrity payload",
		 IPMI_CONN_NAME(ipmi));
	handle_connected(ipmi, EINVAL, addr_num);
	goto out;
    }
    integ = msg->data[24] & 0x3f;

    if ((msg->data[28] != 2) || (msg->data[31] != 8)) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%sipmi_lan.c(got_rmcpp_open_session_rsp): "
		 "Got NULL or invalid confidentiality payload",
		 IPMI_CONN_NAME(ipmi));
	handle_connected(ipmi, EINVAL, addr_num);
	goto out;
    }
    conf = msg->data[32] & 0x3f;

    if (auth >= 0x30) {
	auth_entry_t *e = oem_auth_list;
	while (e) {
	    if ((e->auth_num == auth)
		&& (memcmp(e->iana, lan->oem_iana, 3) == 0))
	    {
		authp = e->auth;
		break;
	    }
	    e = e->next;
	}
    } else
	authp = auths[auth];

    if (!authp) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%sipmi_lan.c(got_rmcpp_open_session_rsp): "
		 "BMC returned an auth algorithm that wasn't supported: %d",
		 IPMI_CONN_NAME(ipmi), auth);
	handle_connected(ipmi, EINVAL, addr_num);
	goto out;
    }

    if (conf >= 0x30) {
	conf_entry_t *e = oem_conf_list;
	while (e) {
	    if ((e->conf_num == conf)
		&& (memcmp(e->iana, lan->oem_iana, 3) == 0))
	    {
		confp = e->conf;
		break;
	    }
	    e = e->next;
	}
    } else
	confp = confs[conf];

    if (!confp) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%sipmi_lan.c(got_rmcpp_open_session_rsp): "
		 "BMC returned a conf algorithm that wasn't supported: %d",
		 IPMI_CONN_NAME(ipmi), conf);
	handle_connected(ipmi, EINVAL, addr_num);
	goto out;
    }

    if (integ >= 0x30) {
	integ_entry_t *e = oem_integ_list;
	while (e) {
	    if ((e->integ_num == integ)
		&& (memcmp(e->iana, lan->oem_iana, 3) == 0))
	    {
		integp = e->integ;
		break;
	    }
	    e = e->next;
	}
    } else
	integp = integs[integ];

    if (!integp) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%sipmi_lan.c(got_rmcpp_open_session_rsp): "
		 "BMC returned an integ algorithm that wasn't supported: %d",
		 IPMI_CONN_NAME(ipmi), integ);
	handle_connected(ipmi, EINVAL, addr_num);
	goto out;
    }

    info = ipmi_mem_alloc(sizeof(*info));
    if (!info) {
	handle_connected(ipmi, ENOMEM, addr_num);
	goto out;
    }

    lan->ip[addr_num].working_conf = conf;
    lan->ip[addr_num].working_integ = integ;
    lan->ip[addr_num].conf_info = confp;
    lan->ip[addr_num].integ_info = integp;

    lan->ip[addr_num].ainfo.lan = lan;
    lan->ip[addr_num].ainfo.role = ((lan->cparm.name_lookup_only << 4)
				    | lan->cparm.privilege);

    info->lan = lan;
    info->rspi = rspi;

    rv = authp->start_auth(ipmi, addr_num, lan->fd_slot,
			   &(lan->ip[addr_num].ainfo),
			   rmcpp_set_info, rmcpp_auth_finished,
			   info);
    if (rv) {
	ipmi_mem_free(info);
	handle_connected(ipmi, rv, addr_num);
	goto out;
    }

    return IPMI_MSG_ITEM_USED;

 out:
    return IPMI_MSG_ITEM_NOT_USED;
}

static int
send_rmcpp_open_session(ipmi_con_t *ipmi, lan_data_t *lan, ipmi_msgi_t *rspi,
			int addr_num)
{
    int               rv;
    unsigned char     data[32];
    ipmi_msg_t        msg;
    ipmi_rmcpp_addr_t addr;

    memset(data, 0, sizeof(data));
    data[0] = 0; /* Set to seq# by the formatting code. */
    data[1] = lan->cparm.privilege;
    ipmi_set_uint32(data+4, lan->ip[addr_num].precon_session_id);
    data[8] = 0; /* auth algorithm */
    if ((int) lan->cparm.auth == IPMI_LANP_AUTHENTICATION_ALGORITHM_BMCPICK)
	data[11] = 0; /* Let the BMC pick */
    else {
	data[11] = 8;
	data[12] = lan->cparm.auth;
    }
    data[16] = 1; /* integrity algorithm */
    if ((int) lan->cparm.integ == IPMI_LANP_INTEGRITY_ALGORITHM_BMCPICK)
	data[19] = 0; /* Let the BMC pick */
    else {
	data[19] = 8;
	data[20] = lan->cparm.integ;
    }
    data[24] = 2; /* confidentiality algorithm */
    if ((int) lan->cparm.conf == IPMI_LANP_CONFIDENTIALITY_ALGORITHM_BMCPICK)
	data[27] = 0; /* Let the BMC pick */
    else {
	data[27] = 8;
	data[28] = lan->cparm.conf;
    }

    msg.netfn = IPMI_RMCPP_DUMMY_NETFN;
    msg.cmd = IPMI_RMCPP_PAYLOAD_TYPE_OPEN_SESSION_REQUEST;
    msg.data = data;
    msg.data_len = 32;
    addr.addr_type = (IPMI_RMCPP_ADDR_START
		      + IPMI_RMCPP_PAYLOAD_TYPE_OPEN_SESSION_REQUEST);

    rv = ipmi_lan_send_command_forceip(ipmi, addr_num,
				       (ipmi_addr_t *) &addr, sizeof(addr),
				       &msg, got_rmcpp_open_session_rsp, rspi);
    return rv;
}

static int
start_rmcpp(ipmi_con_t *ipmi, lan_data_t *lan, ipmi_msgi_t *rspi, int addr_num)
{
    int rv;

    /* We don't really need to get the cipher suites, the user
       requests them (or defaults them to the mandatory ones). */

    lan->ip[addr_num].working_authtype = IPMI_AUTHTYPE_RMCP_PLUS;
    lan->ip[addr_num].outbound_seq_num = 0;
    lan->ip[addr_num].unauth_out_seq_num = 0;
    lan->ip[addr_num].inbound_seq_num = 0;
    lan->ip[addr_num].unauth_in_seq_num = 0;
    /* Use our fd_slot in the fd for the session id, so we can look it
       up quickly. */
    lan->ip[addr_num].precon_session_id = lan->fd_slot + 1;
    lan->ip[addr_num].working_conf = IPMI_LANP_CONFIDENTIALITY_ALGORITHM_NONE;
    lan->ip[addr_num].working_integ = IPMI_LANP_INTEGRITY_ALGORITHM_NONE;

    rv = send_rmcpp_open_session(ipmi, lan, rspi, addr_num);
    if (rv) {
	handle_connected(ipmi, rv, addr_num);
	goto out;
    }

    return IPMI_MSG_ITEM_USED;

 out:
    return IPMI_MSG_ITEM_NOT_USED;
}

static int
session_activated(ipmi_con_t *ipmi, ipmi_msgi_t  *rspi)
{
    ipmi_msg_t *msg = &rspi->msg;
    lan_data_t *lan;
    int        rv;
    int        addr_num = (long) rspi->data4;


    if (!ipmi) {
	handle_connected(ipmi, ECANCELED, addr_num);
	goto out;
    }

    lan = (lan_data_t *) ipmi->con_data;

    if (msg->data[0] != 0) {
        handle_connected(ipmi, IPMI_IPMI_ERR_VAL(msg->data[0]), addr_num);
	goto out;
    }

    if (msg->data_len < 11) {
        handle_connected(ipmi, EINVAL, addr_num);
	goto out;
    }

    lan->ip[addr_num].working_authtype = msg->data[1] & 0xf;
    if ((lan->ip[addr_num].working_authtype != 0)
	&& (lan->ip[addr_num].working_authtype != lan->chosen_authtype))
    {
	/* Eh?  It didn't return a valid authtype. */
        handle_connected(ipmi, EINVAL, addr_num);
	goto out;
    }

    lan->ip[addr_num].session_id = ipmi_get_uint32(msg->data+2);
    lan->ip[addr_num].outbound_seq_num = ipmi_get_uint32(msg->data+6);

    rv = send_set_session_privilege(ipmi, lan, addr_num, rspi);
    if (rv) {
        handle_connected(ipmi, rv, addr_num);
	goto out;
    }

    return IPMI_MSG_ITEM_USED;

 out:
    return IPMI_MSG_ITEM_NOT_USED;
}

static int
send_activate_session(ipmi_con_t *ipmi, lan_data_t *lan, int addr_num,
		      ipmi_msgi_t *rspi)
{
    unsigned char                data[IPMI_MAX_MSG_LENGTH];
    ipmi_msg_t                   msg;
    int                          rv;
    ipmi_system_interface_addr_t addr;

    addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
    addr.channel = 0xf;
    addr.lun = 0;

    data[0] = lan->chosen_authtype;
    data[1] = lan->cparm.privilege;
    memcpy(data+2, lan->challenge_string, 16);
    ipmi_set_uint32(data+18, lan->ip[addr_num].inbound_seq_num);

    msg.cmd = IPMI_ACTIVATE_SESSION_CMD;
    msg.netfn = IPMI_APP_NETFN;
    msg.data = data;
    msg.data_len = 22;

    rv = ipmi_lan_send_command_forceip(ipmi, addr_num,
				       (ipmi_addr_t *) &addr, sizeof(addr),
				       &msg, session_activated, rspi);
    return rv;
}

static int
challenge_done(ipmi_con_t *ipmi, ipmi_msgi_t *rspi)
{
    ipmi_msg_t *msg = &rspi->msg;
    lan_data_t *lan;
    int        rv;
    int        addr_num = (long) rspi->data4;


    if (!ipmi) {
	handle_connected(ipmi, ECANCELED, addr_num);
	goto out;
    }

    lan = (lan_data_t *) ipmi->con_data;

    if (msg->data[0] != 0) {
        handle_connected(ipmi, IPMI_IPMI_ERR_VAL(msg->data[0]), addr_num);
	goto out;
    }

    if (msg->data_len < 21) {
        handle_connected(ipmi, EINVAL, addr_num);
	goto out;
    }

    /* Get the temporary session id. */
    lan->ip[addr_num].session_id = ipmi_get_uint32(msg->data+1);

    lan->ip[addr_num].outbound_seq_num = 0;
    lan->ip[addr_num].working_authtype = lan->chosen_authtype;
    memcpy(lan->challenge_string, msg->data+5, 16);

    /* Get a random number of the other end to start sending me sequence
       numbers at, but don't let it be zero. */
    while (lan->ip[addr_num].inbound_seq_num == 0) {
	rv = ipmi->os_hnd->get_random(ipmi->os_hnd,
				      &(lan->ip[addr_num].inbound_seq_num), 4);
	if (rv) {
	    handle_connected(ipmi, rv, addr_num);
	    goto out;
	}
    }

    rv = send_activate_session(ipmi, lan, addr_num, rspi);
    if (rv) {
        handle_connected(ipmi, rv, addr_num);
	goto out;
    }

    return IPMI_MSG_ITEM_USED;

 out:
    return IPMI_MSG_ITEM_NOT_USED;
}

static int
send_challenge(ipmi_con_t *ipmi, lan_data_t *lan, int addr_num,
	       ipmi_msgi_t *rspi)
{
    unsigned char                data[IPMI_MAX_MSG_LENGTH];
    ipmi_msg_t                   msg;
    ipmi_system_interface_addr_t addr;
    int                          rv;

    addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
    addr.channel = 0xf;
    addr.lun = 0;

    data[0] = lan->chosen_authtype;
    msg.cmd = IPMI_GET_SESSION_CHALLENGE_CMD;
    msg.netfn = IPMI_APP_NETFN;
    msg.data = data;
    msg.data_len = 1;
    memcpy(data+1, lan->cparm.username, IPMI_USERNAME_MAX);
    msg.data_len += IPMI_USERNAME_MAX;

    rv = ipmi_lan_send_command_forceip(ipmi, addr_num,
				       (ipmi_addr_t *) &addr, sizeof(addr),
				       &msg, challenge_done, rspi);
    return rv;
}

static int
auth_cap_done(ipmi_con_t *ipmi, ipmi_msgi_t *rspi)
{
    ipmi_msg_t *msg = &rspi->msg;
    lan_data_t *lan;
    int        rv;
    int        addr_num = (long) rspi->data4;
    int        supports_ipmi2;
    int        extended_capabilities_reported;

    if (!ipmi) {
	handle_connected(ipmi, ECANCELED, addr_num);
	goto out;
    }

    lan = (lan_data_t *) ipmi->con_data;

    if ((msg->data[0] != 0) || (msg->data_len < 9)) {
	handle_connected(ipmi, EINVAL, addr_num);
	goto out;
    }

    extended_capabilities_reported = (msg->data[2] & 0x80);
    supports_ipmi2 = (msg->data[4] & 0x02);
    if (extended_capabilities_reported && supports_ipmi2) {
	/* We have RMCP+ support!  Use it. */
	lan->use_two_keys = (msg->data[3] >> 5) & 1;
	memcpy(lan->oem_iana, msg->data+5, 3);
	lan->oem_aux = msg->data[8];
	return start_rmcpp(ipmi, lan, rspi, addr_num);
    }
    else if (supports_ipmi2)
    {
	/*
	 * The BMC has said that it supports RMCP+/IPMI 2.0 in the
	 * extended response fields, but has not indicated that we
	 * should USE the extended response fields!  The SuperMicro
	 * AOC-IPMI20-E currently does this (April 2005), and will do
	 * so until they provide BMC firmware that supports RMCP+.
	 */
	ipmi_log(IPMI_LOG_WARNING,
		"%sipmi_lan.c(auth_cap_done): "
		"BMC confused about RMCP+ support. Disabling RMCP+.",
		IPMI_CONN_NAME(lan->ipmi));
    } 
    if (lan->cparm.authtype == IPMI_AUTHTYPE_RMCP_PLUS) {
	/* The user specified RMCP+, but the system doesn't have it. */
	ipmi_log(IPMI_LOG_ERR_INFO,
		"%sipmi_lan.c(auth_cap_done): "
		"User requested RMCP+, but not supported",
		IPMI_CONN_NAME(lan->ipmi));
	handle_connected(ipmi, ENOENT, addr_num);
	goto out;
    }

    memcpy(lan->oem_iana, msg->data+5, 3);
    lan->oem_aux = msg->data[8];

    if (lan->authdata) {
	ipmi_auths[lan->chosen_authtype].authcode_cleanup(lan->authdata);
	lan->authdata = NULL;
    }

    if ((int) lan->cparm.authtype == IPMI_AUTHTYPE_DEFAULT) {
	/* Pick the most secure authentication type. */
	if (msg->data[2] & (1 << IPMI_AUTHTYPE_MD5)) {
	    lan->chosen_authtype = IPMI_AUTHTYPE_MD5;
	} else if (msg->data[2] & (1 << IPMI_AUTHTYPE_MD2)) {
	    lan->chosen_authtype = IPMI_AUTHTYPE_MD2;
	} else if (msg->data[2] & (1 << IPMI_AUTHTYPE_STRAIGHT)) {
	    lan->chosen_authtype = IPMI_AUTHTYPE_STRAIGHT;
	} else if (msg->data[2] & (1 << IPMI_AUTHTYPE_NONE)) {
	    lan->chosen_authtype = IPMI_AUTHTYPE_NONE;
	} else {
	    ipmi_log(IPMI_LOG_ERR_INFO,
		     "%sipmi_lan.c(auth_cap_done): "
		     "No valid authentication supported",
		     IPMI_CONN_NAME(lan->ipmi));
	    handle_connected(ipmi, EINVAL, addr_num);
	    goto out;
	}
    } else {
	if (!(msg->data[2] & (1 << lan->cparm.authtype))) {
	    ipmi_log(IPMI_LOG_ERR_INFO,
		     "%sipmi_lan.c(auth_cap_done): "
		     "Requested authentication not supported",
		     IPMI_CONN_NAME(lan->ipmi));
	    handle_connected(ipmi, EINVAL, addr_num);
	    goto out;
	}
	lan->chosen_authtype = lan->cparm.authtype;
    }

    rv = ipmi_auths[lan->chosen_authtype].authcode_init(lan->cparm.password,
							&(lan->authdata),
							NULL, auth_alloc,
							auth_free);
    if (rv) {
        ipmi_log(IPMI_LOG_ERR_INFO,
		 "%sipmi_lan.c(auth_cap_done): "
		 "Unable to initialize authentication data: 0x%x",
		 IPMI_CONN_NAME(lan->ipmi), rv);
        handle_connected(ipmi, rv, addr_num);
	goto out;
    }

    rv = send_challenge(ipmi, lan, addr_num, rspi);
    if (rv) {
        ipmi_log(IPMI_LOG_ERR_INFO,
		 "%sipmi_lan.c(auth_cap_done): "
		 "Unable to send challenge command: 0x%x",
		 IPMI_CONN_NAME(lan->ipmi), rv);
        handle_connected(ipmi, rv, addr_num);
	goto out;
    }

    return IPMI_MSG_ITEM_USED;

 out:
    return IPMI_MSG_ITEM_NOT_USED;
}

static int
auth_cap_done_p(ipmi_con_t *ipmi, ipmi_msgi_t *rspi)
{
    ipmi_msg_t *msg = &rspi->msg;
    lan_data_t *lan;
    int        addr_num = (long) rspi->data4;
    int        rv;

    if (!ipmi) {
	handle_connected(ipmi, ECANCELED, addr_num);
	goto out;
    }

    lan = (lan_data_t *) ipmi->con_data;

    if ((msg->data[0] != 0) || (msg->data_len < 9)) {
	/* Got an error, try it without the RMCP+ bit set.  Some
	   systems incorrectly return errors when reserved data is
	   set. */

	if (lan->cparm.authtype == IPMI_AUTHTYPE_RMCP_PLUS) {
	    /* The user specified RMCP+, but the system doesn't have it. */
	    ipmi_log(IPMI_LOG_ERR_INFO,
		     "%sipmi_lan.c(auth_cap_done_p): "
		     "Use requested RMCP+, but not supported",
		     IPMI_CONN_NAME(lan->ipmi));
	    handle_connected(ipmi, ENOENT, addr_num);
	    goto out;
	}

	rv = send_auth_cap(ipmi, lan, addr_num, 1);
	if (rv) {
	    handle_connected(ipmi, rv, addr_num);
	}
	goto out;
    }


    return auth_cap_done(ipmi, rspi);

 out:
    return IPMI_MSG_ITEM_NOT_USED;
}

static int
send_auth_cap(ipmi_con_t *ipmi, lan_data_t *lan, int addr_num,
	      int force_ipmiv15)
{
    unsigned char                data[2];
    ipmi_msg_t                   msg;
    ipmi_system_interface_addr_t addr;
    int                          rv;
    ipmi_msgi_t                  *rspi;
    ipmi_ll_rsp_handler_t        rsp_handler;

    /* FIXME - a system may only support RMCP+ and not RMCP.  We need
       a way to detect and handle that.  */

    rspi = ipmi_mem_alloc(sizeof(*rspi));
    if (!rspi)
	return ENOMEM;

    addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
    addr.channel = 0xf;
    addr.lun = 0;

    data[0] = 0xe;
    data[1] = lan->cparm.privilege;
    msg.cmd = IPMI_GET_CHANNEL_AUTH_CAPABILITIES_CMD;
    msg.netfn = IPMI_APP_NETFN;
    msg.data = data;
    msg.data_len = 2;
    if ((((int) lan->cparm.authtype == IPMI_AUTHTYPE_DEFAULT)
	 || ((int) lan->cparm.authtype == IPMI_AUTHTYPE_RMCP_PLUS))
	&& !force_ipmiv15)
    {
	rsp_handler = auth_cap_done_p;
	data[0] |= 0x80; /* Get RMCP data. */
    } else {
	rsp_handler = auth_cap_done;
    }

    rv = ipmi_lan_send_command_forceip(ipmi, addr_num,
				       (ipmi_addr_t *) &addr, sizeof(addr),
				       &msg, rsp_handler, rspi);
    if (rv)
	ipmi_mem_free(rspi);
    return rv;
}

static int
lan_start_con(ipmi_con_t *ipmi)
{
    lan_data_t     *lan = (lan_data_t *) ipmi->con_data;
    int            rv;
    struct timeval timeout;
    unsigned int   i;

    ipmi_lock(lan->ip_lock);
    if (lan->started) {
	/* Only allow started to be called once, but make sure the
	   connected callback gets called if started is called again
	   (assuming the connection is up).  This lets multiple users
	   use the same connection.  If the LAN is not connected, this
	   doesn't matter, the callback will be called properly
	   later. */
	if (lan->connected) {
	    unsigned int i;
	    int          port_err[MAX_IP_ADDR];

	    for (i=0; i<lan->cparm.num_ip_addr; i++)
		port_err[i] = lan->ip[i].working ? 0 : EINVAL;

	    ipmi_lock(lan->con_change_lock);
	    ipmi_unlock(lan->ip_lock);

	    for (i=0; i<lan->cparm.num_ip_addr; i++)
		call_con_change_handlers(lan, port_err[i], i, 1);
	    ipmi_unlock(lan->con_change_lock);
	} else
	    ipmi_unlock(lan->ip_lock);
	return 0;
    }

    /* Start the timer to audit the connections. */
    lan->audit_info = ipmi_mem_alloc(sizeof(*(lan->audit_info)));
    if (!lan->audit_info) {
	rv = ENOMEM;
	goto out_err;
    }

    lan->audit_info->cancelled = 0;
    lan->audit_info->ipmi = ipmi;
    rv = ipmi->os_hnd->alloc_timer(ipmi->os_hnd, &(lan->audit_timer));
    if (rv)
	goto out_err;
    timeout.tv_sec = LAN_AUDIT_TIMEOUT / 1000000;
    timeout.tv_usec = LAN_AUDIT_TIMEOUT % 1000000;
    rv = ipmi->os_hnd->start_timer(ipmi->os_hnd,
				   lan->audit_timer,
				   &timeout,
				   audit_timeout_handler,
				   lan->audit_info);
    if (rv) {
	ipmi_mem_free(lan->audit_info);
	lan->audit_info = NULL;
	ipmi->os_hnd->free_timer(ipmi->os_hnd, lan->audit_timer);
	lan->audit_timer = NULL;
	goto out_err;
    }

    lan->started = 1;
    ipmi_unlock(lan->ip_lock);

    for (i=0; i<lan->cparm.num_ip_addr; i++)
	/* Ignore failures, this gets retried. */
	send_auth_cap(ipmi, lan, i, 0);

    return 0;

 out_err:
    ipmi_unlock(lan->ip_lock);
    return rv;
}

int
ipmi_lan_setup_con(struct in_addr            *ip_addrs,
		   int                       *ports,
		   unsigned int              num_ip_addrs,
		   unsigned int              authtype,
		   unsigned int              privilege,
		   void                      *username,
		   unsigned int              username_len,
		   void                      *password,
		   unsigned int              password_len,
		   os_handler_t              *handlers,
		   void                      *user_data,
		   ipmi_con_t                **new_con)
{
    char s_ip_addrs[MAX_IP_ADDR][20];
    char s_ports[MAX_IP_ADDR][10];
    char *paddrs[MAX_IP_ADDR], *pports[MAX_IP_ADDR];
    unsigned char *p;
    unsigned int  i;
    int           rv;

    if ((num_ip_addrs < 1) || (num_ip_addrs > MAX_IP_ADDR))
	return EINVAL;
    for (i=0; i<num_ip_addrs; i++) {
	p = (unsigned char *)&(ip_addrs[i]);
	sprintf(s_ip_addrs[i], "%u.%u.%u.%u", *p, *(p+1), *(p+2), *(p+3));
	sprintf(s_ports[i], "%u", ports[i]);
	paddrs[i] = s_ip_addrs[i];
	pports[i]= s_ports[i];
    }
    rv = ipmi_ip_setup_con(paddrs, 
			   pports, 
			   num_ip_addrs,
			   authtype,
			   privilege,
			   username,
			   username_len,
			   password,
			   password_len,
			   handlers,
			   user_data,
			   new_con);
    return rv;
}

int
ipmi_ip_setup_con(char         * const ip_addrs[],
		  char         * const ports[],
		  unsigned int num_ip_addrs,
		  unsigned int authtype,
		  unsigned int privilege,
		  void         *username,
		  unsigned int username_len,
		  void         *password,
		  unsigned int password_len,
		  os_handler_t *handlers,
		  void         *user_data,
		  ipmi_con_t   **new_con)
{
    ipmi_lanp_parm_t parms[6];

    parms[0].parm_id = IPMI_LANP_PARMID_ADDRS;
    parms[0].parm_data = (void *) ip_addrs;
    parms[0].parm_data_len = num_ip_addrs;
    parms[1].parm_id = IPMI_LANP_PARMID_PORTS;
    parms[1].parm_data = (void *) ports;
    parms[1].parm_data_len = num_ip_addrs;
    parms[2].parm_id = IPMI_LANP_PARMID_AUTHTYPE;
    parms[2].parm_val = authtype;
    parms[3].parm_id = IPMI_LANP_PARMID_PRIVILEGE;
    parms[3].parm_val = privilege;
    parms[4].parm_id = IPMI_LANP_PARMID_USERNAME;
    parms[4].parm_data = username;
    parms[4].parm_data_len = username_len;
    parms[5].parm_id = IPMI_LANP_PARMID_PASSWORD;
    parms[5].parm_data = password;
    parms[5].parm_data_len = password_len;
    return ipmi_lanp_setup_con(parms, 6, handlers, user_data, new_con);
}

static lan_data_t *
find_matching_lan(lan_conn_parms_t *cparm)
{
    lan_link_t   *l;
    lan_data_t   *lan;
    unsigned int idx;

    /* Look in the first IP addresses list. */
    idx = hash_lan_addr(&cparm->ip_addr[0].s_ipsock.s_addr);
    ipmi_lock(lan_list_lock);
    l = lan_ip_list[idx].next;
    while (l->lan) {
	lan = l->lan;
	if (memcmp(&lan->cparm, cparm, sizeof(*cparm)) == 0) {
	    /* Parms match up, use it */
	    lan->users++;
	    ipmi_unlock(lan_list_lock);
	    return lan;
	}
	l = l->next;
    }
    ipmi_unlock(lan_list_lock);
    return NULL;
}

static void
lan_use_connection(ipmi_con_t *ipmi)
{
    lan_data_t *lan = ipmi->con_data;

    ipmi_lock(lan_list_lock);
    lan->users++;
    ipmi_unlock(lan_list_lock);
}

static int
lan_register_stat_handler(ipmi_con_t          *ipmi,
			  ipmi_ll_stat_info_t *info)
{
    lan_stat_info_t *nstat;
    lan_data_t      *lan = ipmi->con_data;
    int             i;

    nstat = ipmi_mem_alloc(sizeof(*nstat));
    if (!nstat)
	return ENOMEM;
    memset(nstat, 0, sizeof(*nstat));

    for (i=0; i<NUM_STATS; i++)
	ipmi_ll_con_stat_call_register(info, lan_stat_names[i],
				       ipmi->name, &(nstat->stats[i]));

    if (!locked_list_add(lan->lan_stat_list, nstat, info)) {
	for (i=0; i<NUM_STATS; i++)
	    if (nstat->stats[i]) {
		ipmi_ll_con_stat_call_unregister(info, nstat->stats[i]);
		nstat->stats[i] = NULL;
	    }
	ipmi_mem_free(nstat);
	return ENOMEM;
    }

    return 0;
}

static int
lan_unregister_stat_handler(ipmi_con_t          *ipmi,
			    ipmi_ll_stat_info_t *info)
{
    lan_unreg_stat_info_t sinfo;
    lan_data_t            *lan = ipmi->con_data;

    sinfo.lan = lan;
    sinfo.cmpinfo = info;
    sinfo.found = 0;
    locked_list_iterate(lan->lan_stat_list, lan_unreg_stat_info,
			&sinfo);
    if (sinfo.found)
	return 0;
    else
	return EINVAL;
}

static ipmi_args_t *get_startup_args(ipmi_con_t *ipmi);

static unsigned int conf_order[] = {
    IPMI_LANP_CONFIDENTIALITY_ALGORITHM_AES_CBC_128
};

static unsigned int
most_secure_lanp_conf(void)
{
    unsigned int i, v;
    for (i=0; i<(sizeof(conf_order)/sizeof(unsigned int)); i++) {
	v = conf_order[i];
	if (confs[v])
	    return v;
    }

    return IPMI_LANP_CONFIDENTIALITY_ALGORITHM_NONE;
}

static unsigned int integ_order[] = {
    IPMI_LANP_INTEGRITY_ALGORITHM_HMAC_SHA1_96,
    IPMI_LANP_INTEGRITY_ALGORITHM_HMAC_MD5_128,
    IPMI_LANP_INTEGRITY_ALGORITHM_MD5_128
};

static unsigned int
most_secure_lanp_integ(void)
{
    unsigned int i, v;
    for (i=0; i<(sizeof(integ_order)/sizeof(unsigned int)); i++) {
	v = integ_order[i];
	if (integs[v])
	    return v;
    }

    return IPMI_LANP_INTEGRITY_ALGORITHM_NONE;
}

static unsigned int auth_order[] = {
    IPMI_LANP_AUTHENTICATION_ALGORITHM_RAKP_HMAC_SHA1,
    IPMI_LANP_AUTHENTICATION_ALGORITHM_RAKP_HMAC_MD5
};

static unsigned int
most_secure_lanp_auth(void)
{
    unsigned int i, v;
    for (i=0; i<(sizeof(auth_order)/sizeof(unsigned int)); i++) {
	v = auth_order[i];
	if (auths[v])
	    return v;
    }

    return IPMI_LANP_AUTHENTICATION_ALGORITHM_RAKP_NONE;
}


int
ipmi_lanp_setup_con(ipmi_lanp_parm_t *parms,
		    unsigned int     num_parms,
		    os_handler_t     *handlers,
		    void             *user_data,
		    ipmi_con_t       **new_con)
{
    ipmi_con_t         *ipmi = NULL;
    lan_data_t         *lan = NULL;
    int                rv;
    unsigned int       i;
    unsigned int       count;
    struct sockaddr_in *pa;
    char               **ip_addrs = NULL;
    char               *tports[MAX_IP_ADDR];
    char               **ports = NULL;
    lan_conn_parms_t   cparm;
    int max_outstanding_msg_count = DEFAULT_MAX_OUTSTANDING_MSG_COUNT;

    memset(&cparm, 0, sizeof(cparm));

    /* Pick some secure defaults. */
    cparm.authtype = IPMI_AUTHTYPE_DEFAULT;
    cparm.privilege = IPMI_PRIVILEGE_ADMIN;
    cparm.conf = most_secure_lanp_conf();
    cparm.integ = most_secure_lanp_integ();
    cparm.auth = most_secure_lanp_auth();
    cparm.name_lookup_only = 1;

    for (i=0; i<num_parms; i++) {
	switch (parms[i].parm_id) {
	case IPMI_LANP_PARMID_AUTHTYPE:
	    cparm.authtype = parms[i].parm_val;
	    break;

	case IPMI_LANP_PARMID_PRIVILEGE:
	    cparm.privilege = parms[i].parm_val;
	    break;

	case IPMI_LANP_PARMID_PASSWORD:
	    if (parms[i].parm_data_len > sizeof(cparm.password))
		return EINVAL;
	    memcpy(cparm.password, parms[i].parm_data, parms[i].parm_data_len);
	    cparm.password_len = parms[i].parm_data_len;
	    break;

	case IPMI_LANP_PARMID_USERNAME:
	    if (parms[i].parm_data_len > sizeof(cparm.username))
		return EINVAL;
	    memcpy(cparm.username, parms[i].parm_data, parms[i].parm_data_len);
	    cparm.username_len = parms[i].parm_data_len;
	    break;

	case IPMI_LANP_PARMID_ADDRS:
	    if (cparm.num_ip_addr
		&& (cparm.num_ip_addr != parms[i].parm_data_len))
		return EINVAL;
	    if (parms[i].parm_data_len > MAX_IP_ADDR)
		return EINVAL;
	    ip_addrs = parms[i].parm_data;
	    cparm.num_ip_addr = parms[i].parm_data_len;
	    break;

	case IPMI_LANP_PARMID_PORTS:
	    if (cparm.num_ip_addr
		&& (cparm.num_ip_addr != parms[i].parm_data_len))
		return EINVAL;
	    if (parms[i].parm_data_len > MAX_IP_ADDR)
		return EINVAL;
	    ports = parms[i].parm_data;
	    cparm.num_ip_addr = parms[i].parm_data_len;
	    break;

	case IPMI_LANP_AUTHENTICATION_ALGORITHM:
	    cparm.auth = parms[i].parm_val;
	    if ((int) cparm.auth != IPMI_LANP_AUTHENTICATION_ALGORITHM_BMCPICK)
	    {
		if (cparm.auth >= 64)
		    return EINVAL;
		if ((cparm.auth < 0x30) && (!auths[cparm.auth]))
		    return ENOSYS;
	    }
	    break;

	case IPMI_LANP_INTEGRITY_ALGORITHM:
	    cparm.integ = parms[i].parm_val;
	    if ((int) cparm.integ != IPMI_LANP_INTEGRITY_ALGORITHM_BMCPICK)
	    {
		if (cparm.integ >= 64)
		    return EINVAL;
		if (cparm.integ
		    && ((cparm.integ < 0x30) && (!integs[cparm.integ])))
		    return ENOSYS;
	    }
	    break;

	case IPMI_LANP_CONFIDENTIALITY_ALGORITHM:
	    cparm.conf = parms[i].parm_val;
	    if ((int)cparm.conf != IPMI_LANP_CONFIDENTIALITY_ALGORITHM_BMCPICK)
	    {
		if (cparm.conf >= 64)
		    return EINVAL;
		if (cparm.conf
		    && ((cparm.conf < 0x30) && (!confs[cparm.conf])))
		    return ENOSYS;
	    }
	    break;

	case IPMI_LANP_NAME_LOOKUP_ONLY:
	    cparm.name_lookup_only = parms[i].parm_val != 0;
	    break;

	case IPMI_LANP_BMC_KEY:
	    if (parms[i].parm_data_len > sizeof(cparm.bmc_key))
		return EINVAL;
	    memcpy(cparm.bmc_key, parms[i].parm_data, parms[i].parm_data_len);
	    cparm.bmc_key_len = parms[i].parm_data_len;
	    break;

	case IPMI_LANP_MAX_OUTSTANDING_MSG_COUNT:
	    if ((parms[i].parm_val < 1)
		|| (parms[i].parm_val > MAX_POSSIBLE_OUTSTANDING_MSG_COUNT))
		return EINVAL;
	    max_outstanding_msg_count = parms[i].parm_val;
	    break;
		
	default:
	    return EINVAL;
	}
    }

    if ((cparm.num_ip_addr == 0) || (ip_addrs == NULL))
	return EINVAL;
    if (((int) cparm.authtype != IPMI_AUTHTYPE_DEFAULT)
	&& (cparm.authtype != IPMI_AUTHTYPE_RMCP_PLUS)
	&& ((cparm.authtype >= MAX_IPMI_AUTHS)
	    || (ipmi_auths[cparm.authtype].authcode_init == NULL)))
	return EINVAL;
    if ((cparm.num_ip_addr < 1) || (cparm.num_ip_addr > MAX_IP_ADDR))
	return EINVAL;

    if (ports) {
	for (i=0; i<MAX_IP_ADDR; i++)
	    tports[i] = ports[i];
	ports = tports;
    } else {
	ports = tports;
	for (i=0; i<MAX_IP_ADDR; i++)
	    ports[i] = NULL;
    }
    for (i=0; i<MAX_IP_ADDR; i++) {
	if (!ports[i])
	    ports[i] = IPMI_LAN_STD_PORT_STR;
    }

    count = 0;
#ifdef HAVE_GETADDRINFO
    for (i=0; i<cparm.num_ip_addr; i++) {
        struct addrinfo hints, *res0;
 
        memset(&hints, 0, sizeof(hints));
        if (count == 0)
            hints.ai_family = PF_UNSPEC;
        else
	{
            /* Make sure all ip address is in the same protocol family*/
	    struct sockaddr_in *paddr;
	    paddr = (struct sockaddr_in *)&(cparm.ip_addr[0]);
            hints.ai_family = paddr->sin_family;
	}
        hints.ai_socktype = SOCK_DGRAM;
        rv = getaddrinfo(ip_addrs[i], ports[i], &hints, &res0);
	if (rv)
	    return EINVAL;

	/* Only get the first choices */
	memcpy(&(cparm.ip_addr[count].s_ipsock), res0->ai_addr,
	       res0->ai_addrlen);
	cparm.ip_addr[count].ip_addr_len = res0->ai_addrlen;
	count++;
	freeaddrinfo(res0);
    }
#else
    /* System does not support getaddrinfo, just for IPv4*/
    for (i=0; i<cparm.num_ip_addr; i++) {
	struct hostent *ent;
	struct sockaddr_in *paddr;
	ent = gethostbyname(ip_addrs[i]);
	if (!ent)
	    return EINVAL;
	paddr = (struct sockaddr_in *)&(cparm.ip_addr[i]);
        paddr->sin_family = AF_INET;
        paddr->sin_port = htons(atoi(ports[i]));
	memcpy(&(paddr->sin_addr), ent->h_addr_list[0], ent->h_length);
	cparm.ip_addr[count].ip_addr_len = ent->h_length;
	count++;
    }
#endif
    if (count == 0)
	return EINVAL;
    cparm.num_ip_addr = count;

    /* At this point we have a validated set of parms in cparm.  See
       if we alreay have one that matches. */
    lan = find_matching_lan(&cparm);
    if (lan) {
	*new_con = lan->ipmi;
	return 0;
    }

    ipmi = ipmi_mem_alloc(sizeof(*ipmi));
    if (!ipmi)
	return ENOMEM;
    memset(ipmi, 0, sizeof(*ipmi));

    ipmi->user_data = user_data;
    ipmi->os_hnd = handlers;
    ipmi->con_type = "rmcp";
    ipmi->priv_level = cparm.privilege;
    for (i=0; i<MAX_IPMI_USED_CHANNELS; i++)
	ipmi->ipmb_addr[i] = 0x20; /* Assume this until told otherwise */

    rv = ipmi_con_attr_init(ipmi);
    if (rv)
	goto out_err;

    lan = ipmi_mem_alloc(sizeof(*lan));
    if (!lan) {
	rv = ENOMEM;
	goto out_err;
    }
    memset(lan, 0, sizeof(*lan));
    ipmi->con_data = lan;
    lan->cparm = cparm;
    for (i=0; i<cparm.num_ip_addr; i++) {
	lan->cparm.ip_addr_str[i] = ipmi_strdup(ip_addrs[i]);
	if (!lan->cparm.ip_addr_str[i]) {
	    rv = ENOMEM;
	    goto out_err;
	}
	lan->cparm.ip_port_str[i] = ipmi_strdup(ports[i]);
	if (!lan->cparm.ip_port_str[i]) {
	    rv = ENOMEM;
	    goto out_err;
	}
    }

    lan->refcount = 1;
    lan->users = 1;
    lan->ipmi = ipmi;
    for (i=0; i<MAX_IPMI_USED_CHANNELS; i++)
	lan->slave_addr[i] = 0x20; /* Assume this until told otherwise */
    lan->is_active = 1;
    lan->chosen_authtype = IPMI_AUTHTYPE_DEFAULT;
    lan->curr_ip_addr = 0;
    lan->num_sends = 0;
    lan->connected = 0;
    lan->initialized = 0;

    lan->outstanding_msg_count = 0;
    lan->max_outstanding_msg_count = max_outstanding_msg_count;
    lan->wait_q = NULL;
    lan->wait_q_tail = NULL;

    pa = (struct sockaddr_in *)&(lan->cparm.ip_addr[0]);
    lan->fd = find_free_lan_fd(pa->sin_family, lan, &lan->fd_slot);
    if (! lan->fd) {
	rv = errno;
	goto out_err;
    }

    /* Create the locks if they are available. */
    rv = ipmi_create_lock_os_hnd(handlers, &lan->seq_num_lock);
    if (rv)
	goto out_err;

    rv = ipmi_create_lock_os_hnd(handlers, &lan->ip_lock);
    if (rv)
	goto out_err;

    lan->con_change_handlers = locked_list_alloc(handlers);
    if (!lan->con_change_handlers) {
	rv = ENOMEM;
	goto out_err;
    }

    lan->event_handlers = locked_list_alloc(handlers);
    if (!lan->event_handlers) {
	rv = ENOMEM;
	goto out_err;
    }

    lan->ipmb_change_handlers = locked_list_alloc(handlers);
    if (!lan->ipmb_change_handlers) {
	rv = ENOMEM;
	goto out_err;
    }

    rv = ipmi_create_lock_os_hnd(handlers, &lan->con_change_lock);
    if (rv)
	goto out_err;

    lan->lan_stat_list = locked_list_alloc(handlers);
    if (!lan->lan_stat_list) {
	rv = ENOMEM;
	goto out_err;
    }

    ipmi->start_con = lan_start_con;
    ipmi->set_ipmb_addr = lan_set_ipmb_addr;
    ipmi->add_ipmb_addr_handler = lan_add_ipmb_addr_handler;
    ipmi->remove_ipmb_addr_handler = lan_remove_ipmb_addr_handler;
    ipmi->add_con_change_handler = lan_add_con_change_handler;
    ipmi->remove_con_change_handler = lan_remove_con_change_handler;
    ipmi->send_command = lan_send_command;
    ipmi->add_event_handler = lan_add_event_handler;
    ipmi->remove_event_handler = lan_remove_event_handler;
    ipmi->send_response = lan_send_response;
    ipmi->register_for_command = lan_register_for_command;
    ipmi->deregister_for_command = lan_deregister_for_command;
    ipmi->close_connection = lan_close_connection;
    ipmi->close_connection_done = lan_close_connection_done;
    ipmi->handle_async_event = handle_async_event;
    ipmi->get_startup_args = get_startup_args;
    ipmi->use_connection = lan_use_connection;
    ipmi->send_command_option = lan_send_command_option;
    ipmi->get_num_ports = lan_get_num_ports;
    ipmi->get_port_info = lan_get_port_info;
    ipmi->register_stat_handler = lan_register_stat_handler;
    ipmi->unregister_stat_handler = lan_unregister_stat_handler;

    /* Add it to the list of valid IPMIs so it will validate.  This
       must be done last, after a point where it cannot fail. */
    lan_add_con(lan);

    *new_con = ipmi;

    return 0;

 out_err:
    cleanup_con(ipmi);
    return rv;
}

static void
snmp_got_match(lan_data_t          *lan,
	       const ipmi_msg_t    *msg,
	       const unsigned char *pet_ack)
{
    ipmi_system_interface_addr_t si;
    ipmi_msg_t                   ack;
    int                          dummy_send_ip;

    si.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
    si.channel = 0xf;
    si.lun = 0;
    handle_async_event(lan->ipmi, (ipmi_addr_t *) &si, sizeof(si), msg);

    /* Send the ack directly. */
    ack.netfn = IPMI_SENSOR_EVENT_NETFN;
    ack.cmd = IPMI_PET_ACKNOWLEDGE_CMD;
    ack.data = (unsigned char *) pet_ack;
    ack.data_len = 12;
    lan_send(lan, (ipmi_addr_t *) &si, sizeof(si), &ack, 0, &dummy_send_ip,
	     NULL);
}

typedef struct lan_do_evt_s
{
    lan_data_t          *lan;
    struct lan_do_evt_s *next;
} lan_do_evt_t;

int
ipmi_lan_handle_external_event(const struct sockaddr *src_addr,
			       const ipmi_msg_t      *msg,
			       const unsigned char   *pet_ack)
{
    lan_link_t   *l;
    lan_data_t   *lan;
    unsigned int i;
    unsigned int idx;
    lan_do_evt_t *found = NULL;
    lan_do_evt_t *next = NULL;

    idx = hash_lan_addr(src_addr);
    ipmi_lock(lan_list_lock);
    l = lan_ip_list[idx].next;
    /* Note that we call all the connections with the given IP
       address, not just the first one we find.  There may be more
       than one. */
    while (l->lan) {
	lan = NULL;
	for (i=0; i<l->lan->cparm.num_ip_addr; i++) {
	    if (l->lan->cparm.ip_addr[i].s_ipsock.s_addr.sa_family
		!= src_addr->sa_family)
	    {
		continue;
	    }
	    switch (src_addr->sa_family)
	    {
	    case PF_INET:
	    {
		struct sockaddr_in *src, *dst;
		src = (struct sockaddr_in *) src_addr;
		dst = &(l->lan->cparm.ip_addr[i].s_ipsock.s_addr4);
		if (dst->sin_addr.s_addr == src->sin_addr.s_addr) {
		    /* We have a match, handle it */
		    lan = l->lan;
		    lan->refcount++;
		}
	    }
	    break;
#ifdef PF_INET6
	    case PF_INET6:
	    {
		struct sockaddr_in6 *src, *dst;
		src = (struct sockaddr_in6 *) src_addr;
		dst = &(l->lan->cparm.ip_addr[i].s_ipsock.s_addr6);
		if (memcmp(dst->sin6_addr.s6_addr,
			   src->sin6_addr.s6_addr,
			   sizeof(struct in6_addr))
		    == 0)
		{
		    /* We have a match, handle it */
		    lan = l->lan;
		    lan->refcount++;
		}
	    }
	    break;
#endif
	    }

	    if (lan) {
		next = ipmi_mem_alloc(sizeof(*next));
		if (!next)
		    /* Can't do anything, just go on.  It's not
		       fatal, it just delays things. */
		    continue;
		next->lan = lan;
		next->next = found;
		found = next;
	    }
	}
	l = l->next;
    }
    ipmi_unlock(lan_list_lock);

    while (found) {
	next = found;
	found = found->next;
	snmp_got_match(next->lan, msg, pet_ack);
	lan_put(next->lan->ipmi);
	ipmi_mem_free(next);
    }

    /* Next will be left non-NULL if something was delivered, it will
       be NULL if nothing was delivered. */
    return next != NULL;
}

typedef struct lan_args_s
{
    char            *str_addr[2];	/* parms 0, 1 */
    char            *str_port[2];	/* parms 2, 3 */
    int             num_addr;
    unsigned int    authtype;		/* parm 4 */
    unsigned int    privilege;		/* parm 5 */
    int             username_set;
    char            username[16];	/* parm 6 */
    unsigned int    username_len;
    int             password_set;
    char            password[20];	/* parm 7 */
    unsigned int    password_len;

    unsigned int    auth_alg;		/* parm 8 */
    unsigned int    integ_alg;		/* parm 9 */
    unsigned int    conf_alg;		/* parm 10 */
    unsigned int    name_lookup_only;	/* parm 11 */
    int             bmc_key_set;
    char            bmc_key[20];	/* parm 12 */
    unsigned int    bmc_key_len;

    unsigned int    hacks;		/* parms 13, 14 */
    unsigned int    max_outstanding_msgs;/* parm 15 */
} lan_args_t;

static const char *auth_range[] = { "default", "none", "md2", "md5",
				    "straight", "oem", "rmcp+", NULL };
static int auth_vals[] = { IPMI_AUTHTYPE_DEFAULT,
			   IPMI_AUTHTYPE_NONE,
			   IPMI_AUTHTYPE_MD2,
			   IPMI_AUTHTYPE_MD5,
			   IPMI_AUTHTYPE_STRAIGHT,
			   IPMI_AUTHTYPE_OEM,
			   IPMI_AUTHTYPE_RMCP_PLUS };

static const char *priv_range[] = { "callback", "user", "operator", "admin",
				    "oem", NULL };
static int priv_vals[] = { IPMI_PRIVILEGE_CALLBACK,
			   IPMI_PRIVILEGE_USER,
			   IPMI_PRIVILEGE_OPERATOR,
			   IPMI_PRIVILEGE_ADMIN,
			   IPMI_PRIVILEGE_OEM };

static const char *auth_alg_range[] = { "bmcpick", "rakp_none",
					"rakp_hmac_sha1", "rakp_hmac_md5",
					NULL };
static int auth_alg_vals[] = { IPMI_LANP_AUTHENTICATION_ALGORITHM_BMCPICK,
			       IPMI_LANP_AUTHENTICATION_ALGORITHM_RAKP_NONE,
			       IPMI_LANP_AUTHENTICATION_ALGORITHM_RAKP_HMAC_SHA1,
			       IPMI_LANP_AUTHENTICATION_ALGORITHM_RAKP_HMAC_MD5 };
static const char *integ_alg_range[] = { "bmcpick", "none", "hmac_sha1",
					"hmac_md5", "md5", NULL };
static int integ_alg_vals[] = { IPMI_LANP_INTEGRITY_ALGORITHM_BMCPICK,
				IPMI_LANP_INTEGRITY_ALGORITHM_NONE,
				IPMI_LANP_INTEGRITY_ALGORITHM_HMAC_SHA1_96,
				IPMI_LANP_INTEGRITY_ALGORITHM_HMAC_MD5_128,
				IPMI_LANP_INTEGRITY_ALGORITHM_MD5_128 };

static const char *conf_alg_range[] = { "bmcpick", "none", "aes_cbc_128",
					"xrc4_128", "xrc4_40", NULL };
static int conf_alg_vals[] = { IPMI_LANP_CONFIDENTIALITY_ALGORITHM_BMCPICK,
			       IPMI_LANP_CONFIDENTIALITY_ALGORITHM_NONE,
			       IPMI_LANP_CONFIDENTIALITY_ALGORITHM_AES_CBC_128,
			       IPMI_LANP_CONFIDENTIALITY_ALGORITHM_xRC4_128,
			       IPMI_LANP_CONFIDENTIALITY_ALGORITHM_xRC4_40 };

static struct lan_argnum_info_s
{
    const char *name;
    const char *type;
    const char *help;
    const char **range;
    const int  *values;
} lan_argnum_info[17] =
{
    { "Address",	"str",
      "*IP name or address of the MC",
      NULL, NULL },
    { "Port",		"str",
      "*IP port or port name for the MC",
      NULL, NULL },
    { "Address2",	"str",
      "IP name or address of a second connection to the same MC",
      NULL, NULL },
    { "Port2",		"str",
      "IP port or portname for a second connection to the same MC",
      NULL, NULL },
    { "Authtype",	"enum",
      "Authentication to use for the connection",
      auth_range, auth_vals },
    { "Privilege",	"enum",
      "Privilege level to use for the connection",
      priv_range, priv_vals },
    { "Username",	"str",
      "The user name to use for the connection",
      NULL, NULL },
    { "Password",	"str",
      "!The password to use for the connection",
      NULL, NULL },
    { "Authentication_Algorithm",	"enum",
      "Authentication algorithm to use for the connection, for RMCP+ only",
      auth_alg_range, auth_alg_vals },
    { "Integrity_Algorithm",	"enum",
      "Integrity algorithm to use for the connection, for RMCP+ only",
      integ_alg_range, integ_alg_vals },
    { "Confidentiality_Algorithm",	"enum",
      "Confidentiality algorithm to use for the connection, for RMCP+ only",
      conf_alg_range, conf_alg_vals },
    { "Name_Lookup_Only",	"bool",
      "Use only the name, not the privilege, for selecting the password",
      NULL, NULL },
    { "BMC_Key",		"str",
      "The key to use for connecting to the BMC, may or may not be required",
      NULL, NULL },
    { "RAKP3_Wrong_RoleM",	"bool",
      "Some systems use the wrong RoleM value for RAKP3, a common problem",
      NULL, NULL },
    { "RMCP_Integ_SIK",		"bool",
      "The IPMI 2.0 Spec was unclear which integrity key to use",
      NULL, NULL },
    { "Max_Outstanding_Msgs",	"int",
      "How many outstanding messages on the connection, range 1-63",
      NULL, NULL },

    { NULL },
};

static ipmi_args_t *lan_con_alloc_args(void);

static void
lan_free_args(ipmi_args_t *args)
{
    lan_args_t *largs = _ipmi_args_get_extra_data(args);

    if (largs->str_addr[0])
	ipmi_mem_free(largs->str_addr[0]);
    if (largs->str_addr[1])
	ipmi_mem_free(largs->str_addr[1]);
    if (largs->str_port[0])
	ipmi_mem_free(largs->str_port[0]);
    if (largs->str_port[1])
	ipmi_mem_free(largs->str_port[1]);
    /* paranoia */
    memset(largs->password, 0, sizeof(largs->password));
    memset(largs->bmc_key, 0, sizeof(largs->bmc_key));
}

static ipmi_args_t *
get_startup_args(ipmi_con_t *ipmi)
{
    ipmi_args_t      *args;
    lan_args_t       *largs;
    lan_data_t       *lan;
    lan_conn_parms_t *cparm;

    args = lan_con_alloc_args();
    if (! args)
	return NULL;
    largs = _ipmi_args_get_extra_data(args);
    lan = (lan_data_t *) ipmi->con_data;
    cparm = &lan->cparm;
    largs->str_addr[0] = ipmi_strdup(cparm->ip_addr_str[0]);
    if (!largs->str_addr[0])
	goto out_err;
    largs->str_port[0] = ipmi_strdup(cparm->ip_port_str[0]);
    if (!largs->str_port[0])
	goto out_err;
    if (cparm->num_ip_addr > 1) {
	largs->str_addr[1] = ipmi_strdup(cparm->ip_addr_str[1]);
	if (!largs->str_addr[1])
	    goto out_err;
	largs->str_port[1] = ipmi_strdup(cparm->ip_port_str[1]);
	if (!largs->str_port[1])
	    goto out_err;
    }
    largs->num_addr = cparm->num_ip_addr;
    largs->authtype = cparm->authtype;
    largs->privilege = cparm->privilege;
    if (cparm->username_len) {
	largs->username_len = cparm->username_len ;
	memcpy(largs->username, cparm->username, cparm->username_len);
	largs->username_set = 1;
    }
    if (cparm->password_len) {
	largs->password_len = cparm->password_len ;
	memcpy(largs->password, cparm->password, cparm->password_len);
	largs->password_set = 1;
    }
    largs->conf_alg = cparm->conf;
    largs->auth_alg = cparm->auth;
    largs->integ_alg = cparm->integ;
    largs->name_lookup_only = cparm->name_lookup_only;
    largs->hacks = ipmi->hacks;
    if (cparm->bmc_key_len) {
	largs->bmc_key_len = cparm->bmc_key_len ;
	memcpy(largs->bmc_key, cparm->bmc_key, cparm->bmc_key_len);
	largs->bmc_key_set = 1;
    }
    largs->max_outstanding_msgs = lan->max_outstanding_msg_count;
    return args;

 out_err:
    lan_free_args(args);
    return NULL;
}

static int
lan_connect_args(ipmi_args_t  *args,
		 os_handler_t *handlers,
		 void         *user_data,
		 ipmi_con_t   **con)
{
    lan_args_t       *largs = _ipmi_args_get_extra_data(args);
    int              i;
    ipmi_lanp_parm_t parms[12];
    int              rv;

    i = 0;
    parms[i].parm_id = IPMI_LANP_PARMID_ADDRS;
    parms[i].parm_data = largs->str_addr;
    parms[i].parm_data_len = largs->num_addr;
    i++;
    parms[i].parm_id = IPMI_LANP_PARMID_PORTS;
    parms[i].parm_data = largs->str_port;
    parms[i].parm_data_len = largs->num_addr;
    i++;
    parms[i].parm_id = IPMI_LANP_PARMID_AUTHTYPE;
    parms[i].parm_val = largs->authtype;
    i++;
    parms[i].parm_id = IPMI_LANP_PARMID_PRIVILEGE;
    parms[i].parm_val = largs->privilege;
    i++;
    if (largs->username_set) {
	parms[i].parm_id = IPMI_LANP_PARMID_USERNAME;
	parms[i].parm_data = largs->username;
	parms[i].parm_data_len = largs->username_len;
	i++;
    }
    if (largs->password_set) {
	parms[i].parm_id = IPMI_LANP_PARMID_PASSWORD;
	parms[i].parm_data = largs->password;
	parms[i].parm_data_len = largs->password_len;
	i++;
    }
    parms[i].parm_id = IPMI_LANP_AUTHENTICATION_ALGORITHM;
    parms[i].parm_val = largs->auth_alg;
    i++;
    parms[i].parm_id = IPMI_LANP_INTEGRITY_ALGORITHM;
    parms[i].parm_val = largs->integ_alg;
    i++;
    parms[i].parm_id = IPMI_LANP_CONFIDENTIALITY_ALGORITHM;
    parms[i].parm_val = largs->conf_alg;
    i++;
    parms[i].parm_id = IPMI_LANP_NAME_LOOKUP_ONLY;
    parms[i].parm_val = largs->name_lookup_only;
    i++;
    if (largs->bmc_key_set) {
	parms[i].parm_id = IPMI_LANP_BMC_KEY;
	parms[i].parm_data = largs->bmc_key;
	parms[i].parm_data_len = largs->bmc_key_len;
	i++;
    }
    parms[i].parm_id = IPMI_LANP_MAX_OUTSTANDING_MSG_COUNT;
    parms[i].parm_val = largs->max_outstanding_msgs;
    i++;
    rv = ipmi_lanp_setup_con(parms, i, handlers, user_data, con);
    if (!rv)
	(*con)->hacks = largs->hacks;
    return rv;
}

static int
get_str_val(char **dest, const char *data, int *is_set, unsigned int *len)
{
    char *rval = NULL;
    if (!dest)
	return 0;
    if (is_set && (! *is_set)) {
	*dest = NULL;
	return 0;
    }
    if (data) {
	if (len) {
	    rval = ipmi_mem_alloc(*len+1);
	    if (!rval)
		return ENOMEM;
	    memcpy(rval, data, *len);
	    rval[*len] = '\0';
	} else {
	    rval = ipmi_strdup(data);
	    if (!rval)
		return ENOMEM;
	}
	*dest = rval;
    } else {
	*dest = NULL;
    }
    return 0;
}

static int
get_enum_val(int argnum, char **dest, int data, const char ***rrange)
{
    char       *rval = NULL;
    const int  *values;
    const char **range;
    int        i;

    if (rrange)
	*rrange = lan_argnum_info[argnum].range;

    if (!dest)
	return 0;

    values = lan_argnum_info[argnum].values;
    range = lan_argnum_info[argnum].range;
    for (i=0; range[i]; i++) {
	if (values[i] == data) {
	    rval = ipmi_strdup(lan_argnum_info[argnum].range[i]);
	    if (!rval)
		return ENOMEM;
	    *dest = rval;
	    return 0;
	}
    }
    return EINVAL;
}

static int
get_bool_val(char **dest, int data, unsigned int bit)
{
    char *rval = NULL;

    if (!dest)
	return 0;
    if (data & bit)
	rval = ipmi_strdup("true");
    else
	rval = ipmi_strdup("false");
    if (!rval)
	return ENOMEM;
    *dest = rval;
    return 0;
}

static int
get_int_val(char **dest, int data)
{
    char *rval = NULL;
    int len;

    if (!dest)
	return 0;
    len = snprintf(NULL, 0, "%d", data);
    rval = malloc(len+1);
    if (!rval)
	return ENOMEM;
    snprintf(rval, len+1, "%d", data);
    *dest = rval;
    return 0;
}

static const char *
lan_args_get_type(ipmi_args_t *args)
{
    return "lan";
}

static int
lan_args_get_val(ipmi_args_t  *args,
		 unsigned int argnum,
		 const char   **name,
		 const char   **type,
		 const char   **help,
		 char         **value,
		 const char   ***range)
{
    lan_args_t *largs = _ipmi_args_get_extra_data(args);
    int        rv;

    switch(argnum) {
    case 0:
	rv = get_str_val(value, largs->str_addr[0], NULL, NULL);
	break;

    case 1:
	rv = get_str_val(value, largs->str_port[0], NULL, NULL);
	break;

    case 2:
	rv = get_str_val(value, largs->str_addr[1], NULL, NULL);
	break;

    case 3:
	rv = get_str_val(value, largs->str_port[1], NULL, NULL);
	break;

    case 4:
	rv = get_enum_val(argnum, value, largs->authtype, range);
	break;

    case 5:
	rv = get_enum_val(argnum, value, largs->privilege, range);
	break;

    case 6:
	rv = get_str_val(value, largs->username, &largs->username_set,
			 &largs->username_len);
	break;

    case 7:
	rv = get_str_val(value, largs->password, &largs->password_set,
			 &largs->password_len);
	break;

    case 8:
	rv = get_enum_val(argnum, value, largs->auth_alg, range);
	break;

    case 9:
	rv = get_enum_val(argnum, value, largs->integ_alg, range);
	break;

    case 10:
	rv = get_enum_val(argnum, value, largs->conf_alg, range);
	break;

    case 11:
	rv = get_bool_val(value, largs->name_lookup_only, 1);
	break;

    case 12:
	rv = get_str_val(value, largs->bmc_key, &largs->bmc_key_set,
			 &largs->bmc_key_len);
	break;

    case 13:
	rv = get_bool_val(value, largs->hacks,
			  IPMI_CONN_HACK_RAKP3_WRONG_ROLEM);
	break;

    case 14:
	rv = get_bool_val(value, largs->hacks,
			  IPMI_CONN_HACK_RMCPP_INTEG_SIK);
	break;

    case 15:
	rv = get_int_val(value, largs->max_outstanding_msgs);
	break;

    default:
	return E2BIG;
    }

    if (rv)
	return rv;

    if (name)
	*name = lan_argnum_info[argnum].name;
    if (type)
	*type = lan_argnum_info[argnum].type;
    if (help)
	*help = lan_argnum_info[argnum].help;

    return 0;
}

static int
set_str_val(char **dest, const char *value, int null_ok, int *is_set,
	    unsigned int *len, unsigned int max_len)
{
    char *rval;

    if (! value) {
	if (! null_ok)
	    return EINVAL;
	*dest = NULL;
	if (is_set)
	    *is_set = 0;
	return 0;
    }

    if (len) {
	unsigned int nlen = strlen(value);
	if (nlen > max_len)
	    return EINVAL;
	memcpy(*dest, value, nlen);
	*len = nlen;
    } else {
	rval = ipmi_strdup(value);
	if (!rval)
	    return ENOMEM;
	if (*dest)
	    ipmi_mem_free(*dest);
	*dest = rval;
    }
    if (is_set)
	*is_set = 1;
    return 0;
}

static int
set_enum_val(int argnum, unsigned int *dest, const char *value)
{
    const char **range;
    int        i;

    if (! value)
	return EINVAL;

    range = lan_argnum_info[argnum].range;
    for (i=0; range[i]; i++) {
	if (strcmp(range[i], value) == 0) {
	    *dest = lan_argnum_info[argnum].values[i];
	    return 0;
	}
    }
    return EINVAL;
}

static int
set_bool_val(unsigned int *dest, const char *value, unsigned int bit)
{
    if (! value)
	return EINVAL;

    if (strcmp(value, "true") == 0)
	*dest |= bit;
    else if (strcmp(value, "false") == 0)
	*dest &= ~bit;
    else
	return EINVAL;
    return 0;
}

static int
set_uint_val(unsigned int *dest, const char *value)
{
    int val;
    char *end;

    if (! value)
	return EINVAL;
    if (*value == '\0')
	return EINVAL;

    val = strtoul(value, &end, 0);
    if (*end != '\0')
	return EINVAL;
    *dest = val;
    return 0;
}

static int
lan_args_set_val(ipmi_args_t  *args,
		 unsigned int argnum,
		 const char   *name,
		 const char   *value)
{
    lan_args_t   *largs = _ipmi_args_get_extra_data(args);
    int          rv;
    char         *sval;

    if (name) {
	int i;
	for (i=0; lan_argnum_info[i].name; i++) {
	    if (strcmp(lan_argnum_info[i].name, name) == 0)
		break;
	}
	if (! lan_argnum_info[i].name)
	    return EINVAL;
	argnum = i;
    }

    switch (argnum) {
    case 0:
	rv = set_str_val(&(largs->str_addr[0]), value, 0, NULL, NULL, 0);
	if (!rv && (largs->num_addr == 0))
	    largs->num_addr = 1;
	break;

    case 1:
	rv = set_str_val(&(largs->str_port[0]), value, 1, NULL, NULL, 0);
	break;

    case 2:
	rv = set_str_val(&(largs->str_addr[1]), value, 1, NULL, NULL, 0);
	if (!rv) {
	    if (largs->str_addr[1]) {
		if (largs->num_addr < 2)
		    largs->num_addr = 2;
	    } else {
		if (largs->str_addr[0])
		    largs->num_addr = 1;
		else
		    largs->num_addr = 0;
	    }
	}
	break;

    case 3:
	rv = set_str_val(&(largs->str_port[1]), value, 1, NULL, NULL, 0);
	break;

    case 4:
	rv = set_enum_val(argnum, &largs->authtype, value);
	break;

    case 5:
	rv = set_enum_val(argnum, &largs->privilege, value);
	break;

    case 6:
	sval = largs->username;
	rv = set_str_val(&sval, value, 1, &largs->username_set,
			 &largs->username_len, 16);
	break;

    case 7:
	sval = largs->password;
	rv = set_str_val(&sval, value, 1, &largs->password_set,
			 &largs->password_len, 20);
	break;

    case 8:
	rv = set_enum_val(argnum, &largs->auth_alg, value);
	break;

    case 9:
	rv = set_enum_val(argnum, &largs->integ_alg, value);
	break;

    case 10:
	rv = set_enum_val(argnum, &largs->conf_alg, value);
	break;

    case 11:
	rv = set_bool_val(&largs->name_lookup_only, value, 1);
	break;

    case 12:
	sval = largs->bmc_key;
	rv = set_str_val(&sval, value, 1, &largs->bmc_key_set,
			 &largs->bmc_key_len, 20);
	break;

    case 13:
	rv = set_bool_val(&largs->hacks, value,
			  IPMI_CONN_HACK_RAKP3_WRONG_ROLEM);
	break;

    case 14:
	rv = set_bool_val(&largs->hacks, value,
			  IPMI_CONN_HACK_RMCPP_INTEG_SIK);
	break;

    case 15:
	rv = set_uint_val(&largs->max_outstanding_msgs, value);
	break;

    default:
	rv = E2BIG;
    }

    return rv;
}

static ipmi_args_t *
lan_args_copy(ipmi_args_t *args)
{
    ipmi_args_t *nargs;
    lan_args_t  *largs = _ipmi_args_get_extra_data(args);
    lan_args_t  *nlargs;

    nargs = lan_con_alloc_args();
    if (!nargs)
	return NULL;
    nlargs = _ipmi_args_get_extra_data(nargs);
    *nlargs = *largs;

    nlargs->str_addr[0] = NULL;
    nlargs->str_addr[1] = NULL;
    nlargs->str_port[0] = NULL;
    nlargs->str_port[1] = NULL;

    nlargs->str_addr[0] = ipmi_strdup(largs->str_addr[0]);
    if (! nlargs->str_addr[0])
	goto out_err;
    nlargs->str_addr[1] = ipmi_strdup(largs->str_addr[1]);
    if (! nlargs->str_addr[1])
	goto out_err;
    nlargs->str_port[0] = ipmi_strdup(largs->str_port[0]);
    if (! nlargs->str_port[0])
	goto out_err;
    nlargs->str_port[1] = ipmi_strdup(largs->str_port[1]);
    if (! nlargs->str_port[1])
	goto out_err;
    
    return nargs;

 out_err:
    lan_free_args(nargs);
    return NULL;
}

static int
lan_args_validate(ipmi_args_t *args, int *argnum)
{
    return 1; /* Can't be invalid */
}

static void
lan_args_free_val(ipmi_args_t *args, char *value)
{
    ipmi_mem_free(value);
}

#define CHECK_ARG \
    do { \
        if (*curr_arg >= arg_count) { \
	    rv = EINVAL; \
	    goto out_err; \
        } \
    } while(0)

static int
lan_parse_args(int         *curr_arg,
	       int         arg_count,
	       char        * const *args,
	       ipmi_args_t **iargs)
{
    int         rv;
    ipmi_args_t *p = NULL;
    lan_args_t  *largs;
    int         i;
    int         len;

    CHECK_ARG;

    p = lan_con_alloc_args();
    if (!p)
	return ENOMEM;

    largs = _ipmi_args_get_extra_data(p);
    largs->num_addr = 1;

    while (*curr_arg < arg_count) {
	if (args[*curr_arg][0] != '-') {
	    break;
	}

	if (strcmp(args[*curr_arg], "-U") == 0) {
	    (*curr_arg)++; CHECK_ARG;
	    len = strlen(args[*curr_arg]);
	    if (len > 16)
		len = 16;
	    memcpy(largs->username, args[*curr_arg], len);
	    largs->username_set = 1;
	    largs->username_len = len;
	} else if (strcmp(args[*curr_arg], "-P") == 0) {
	    (*curr_arg)++; CHECK_ARG;
	    len = strlen(args[*curr_arg]);
	    if (len > 20)
		len = 20;
	    memcpy(largs->password, args[*curr_arg], len);
	    largs->password_set = 1;
	    largs->password_len = len;
	} else if (strcmp(args[*curr_arg], "-H") == 0) {
	    (*curr_arg)++; CHECK_ARG;
	    if (strcmp(args[*curr_arg], "intelplus") == 0)
		largs->hacks |= IPMI_CONN_HACK_RAKP3_WRONG_ROLEM;
	    else if (strcmp(args[*curr_arg], "rakp3_wrong_rolem") == 0)
		largs->hacks |= IPMI_CONN_HACK_RAKP3_WRONG_ROLEM;
	    else if (strcmp(args[*curr_arg], "rmcpp_integ_sik") == 0)
		largs->hacks |= IPMI_CONN_HACK_RMCPP_INTEG_SIK;
	    /* Ignore unknown hacks. */
	} else if (strcmp(args[*curr_arg], "-s") == 0) {
	    largs->num_addr = 2;
	} else if (strcmp(args[*curr_arg], "-A") == 0) {
	    (*curr_arg)++; CHECK_ARG;
	    if (strcmp(args[*curr_arg], "none") == 0) {
		largs->authtype = IPMI_AUTHTYPE_NONE;
	    } else if (strcmp(args[*curr_arg], "md2") == 0) {
		largs->authtype = IPMI_AUTHTYPE_MD2;
	    } else if (strcmp(args[*curr_arg], "md5") == 0) {
		largs->authtype = IPMI_AUTHTYPE_MD5;
	    } else if (strcmp(args[*curr_arg], "straight") == 0) {
		largs->authtype = IPMI_AUTHTYPE_STRAIGHT;
	    } else if (strcmp(args[*curr_arg], "rmcp+") == 0) {
		largs->authtype = IPMI_AUTHTYPE_RMCP_PLUS;
	    } else {
		rv = EINVAL;
		goto out_err;
	    }
	} else if (strcmp(args[*curr_arg], "-L") == 0) {
	    (*curr_arg)++; CHECK_ARG;

	    if (strcmp(args[*curr_arg], "callback") == 0) {
		largs->privilege = IPMI_PRIVILEGE_CALLBACK;
	    } else if (strcmp(args[*curr_arg], "user") == 0) {
		largs->privilege = IPMI_PRIVILEGE_USER;
	    } else if (strcmp(args[*curr_arg], "operator") == 0) {
		largs->privilege = IPMI_PRIVILEGE_OPERATOR;
	    } else if (strcmp(args[*curr_arg], "admin") == 0) {
		largs->privilege = IPMI_PRIVILEGE_ADMIN;
	    } else if (strcmp(args[*curr_arg], "oem") == 0) {
		largs->privilege = IPMI_PRIVILEGE_OEM;
	    } else {
		rv = EINVAL;
		goto out_err;
	    }
	} else if (strcmp(args[*curr_arg], "-p") == 0) {
	    (*curr_arg)++; CHECK_ARG;
	    largs->str_port[0] = ipmi_strdup(args[*curr_arg]);
	    if (largs->str_port[0] == NULL) {
		rv = ENOMEM;
		goto out_err;
	    }
	} else if (strcmp(args[*curr_arg], "-p2") == 0) {
	    (*curr_arg)++; CHECK_ARG;
	    largs->str_port[1] = ipmi_strdup(args[*curr_arg]);
	    if (largs->str_port[1] == NULL) {
		rv = ENOMEM;
		goto out_err;
	    }
	} else if (strcmp(args[*curr_arg], "-Ra") == 0) {
	    (*curr_arg)++; CHECK_ARG;

	    if (strcmp(args[*curr_arg], "bmcpick") == 0) {
		largs->auth_alg = IPMI_LANP_AUTHENTICATION_ALGORITHM_BMCPICK;
	    } else if (strcmp(args[*curr_arg], "rakp_none") == 0) {
		largs->auth_alg = IPMI_LANP_AUTHENTICATION_ALGORITHM_RAKP_NONE;
	    } else if (strcmp(args[*curr_arg], "rakp_hmac_sha1") == 0) {
		largs->auth_alg = IPMI_LANP_AUTHENTICATION_ALGORITHM_RAKP_HMAC_SHA1;
	    } else if (strcmp(args[*curr_arg], "rakp_hmac_md5") == 0) {
		largs->auth_alg = IPMI_LANP_AUTHENTICATION_ALGORITHM_RAKP_HMAC_MD5;
	    } else {
		rv = EINVAL;
		goto out_err;
	    }
	} else if (strcmp(args[*curr_arg], "-Ri") == 0) {
	    (*curr_arg)++; CHECK_ARG;

	    if (strcmp(args[*curr_arg], "bmcpick") == 0) {
		largs->integ_alg = IPMI_LANP_INTEGRITY_ALGORITHM_BMCPICK;
	    } else if (strcmp(args[*curr_arg], "none") == 0) {
		largs->integ_alg = IPMI_LANP_INTEGRITY_ALGORITHM_NONE;
	    } else if (strcmp(args[*curr_arg], "hmac_sha1") == 0) {
		largs->integ_alg = IPMI_LANP_INTEGRITY_ALGORITHM_HMAC_SHA1_96;
	    } else if (strcmp(args[*curr_arg], "hmac_md5") == 0) {
		largs->integ_alg = IPMI_LANP_INTEGRITY_ALGORITHM_HMAC_MD5_128;
	    } else if (strcmp(args[*curr_arg], "md5") == 0) {
		largs->integ_alg = IPMI_LANP_INTEGRITY_ALGORITHM_MD5_128;
	    } else {
		rv = EINVAL;
		goto out_err;
	    }
	} else if (strcmp(args[*curr_arg], "-Rc") == 0) {
	    (*curr_arg)++; CHECK_ARG;

	    if (strcmp(args[*curr_arg], "bmcpick") == 0) {
		largs->conf_alg = IPMI_LANP_CONFIDENTIALITY_ALGORITHM_BMCPICK;
	    } else if (strcmp(args[*curr_arg], "none") == 0) {
		largs->conf_alg = IPMI_LANP_CONFIDENTIALITY_ALGORITHM_NONE;
	    } else if (strcmp(args[*curr_arg], "aes_cbc_128") == 0) {
		largs->conf_alg = IPMI_LANP_CONFIDENTIALITY_ALGORITHM_AES_CBC_128;
	    } else if (strcmp(args[*curr_arg], "xrc4_128") == 0) {
		largs->conf_alg = IPMI_LANP_CONFIDENTIALITY_ALGORITHM_xRC4_128;
	    } else if (strcmp(args[*curr_arg], "xrc4_40") == 0) {
		largs->conf_alg = IPMI_LANP_CONFIDENTIALITY_ALGORITHM_xRC4_40;
	    } else {
		rv = EINVAL;
		goto out_err;
	    }
	} else if (strcmp(args[*curr_arg], "-Rl") == 0) {
	    largs->name_lookup_only = 0;
	} else if (strcmp(args[*curr_arg], "-Rk") == 0) {
	    (*curr_arg)++; CHECK_ARG;
	    len = strlen(args[*curr_arg]);
	    if (len > 20)
		len = 20;
	    memcpy(largs->bmc_key, args[*curr_arg], len);
	    largs->bmc_key_set = 1;
	    largs->bmc_key_len = len;
	} else if (strcmp(args[*curr_arg], "-M") == 0) {
	    char *end;
	    int val;
	    (*curr_arg)++; CHECK_ARG;
	    if (args[*curr_arg][0] == '\0') {
		rv = EINVAL;
		goto out_err;
	    }
	    val = strtol(args[*curr_arg], &end, 0);
	    if (*end != '\0') {
		rv = EINVAL;
		goto out_err;
	    }
	    largs->max_outstanding_msgs = val;
	}
	(*curr_arg)++;
    }

    for (i=0; i<largs->num_addr; i++) {
	CHECK_ARG;
	largs->str_addr[i] = ipmi_strdup(args[*curr_arg]);
	if (largs->str_addr[i] == NULL) {
	    rv = ENOMEM;
	    goto out_err;
	}
	(*curr_arg)++;
	if (! largs->str_port[i]) {
	    largs->str_port[i] = ipmi_strdup("623");
	    if (largs->str_port[i] == NULL) {
		rv = ENOMEM;
		goto out_err;
	    }
	}
    }

    *iargs = p;
    return 0;

 out_err:
    if (p)
	ipmi_free_args(p);
    return rv;
}

static const char *
lan_parse_help(void)
{
    return
	"\n"
	" lan [-U <username>] [-P <password>] [-p[2] port] [-A <authtype>]\n"
	"     [-L <privilege>] [-s] [-Ra <auth alg>] [-Ri <integ alg>]\n"
	"     [-Rc <conf algo>] [-Rl] [-Rk <bmc key>] [-H <hackname>]\n"
	"     [-M <max outstanding msgs>] <host1> [<host2>]\n"
	"If -s is supplied, then two host names are taken (the second port\n"
	"may be specified with -p2).  Otherwise, only one hostname is\n"
	"taken.  The defaults are an empty username and password (anonymous),\n"
	"port 623, admin privilege, and authtype defaulting to the most\n"
	"secure one available.\n"
	"privilege is one of: callback, user, operator, admin, or oem.  These\n"
	"select the specific commands that are available to the connection.\n"
	"Higher privileges (ones further to the right in the above list) have\n"
	"more commands available to them.\n"
	"authtype is one of the following: rmcp+, md5, md2, straight, or none.\n"
	"Setting this to anything but rmcp+ forces normal rmcp\n"
	"authentication.  By default the most secure method available is\n"
	"chosen, in the order given above.\n"
	"For RMCP+ connections, the authentication algorithms supported (-Ra)\n"
	"are: bmcpick, rakp_none, rakp_hmac_sha1, and rakp_hmac_md5.  The\n"
	"integrity algorithms (-Ri) supported are: bmcpick, none, hmac_sha1,\n"
	"hmac_md5, and md5.  The confidentiality algorithms (-Rc) are: bmcpick,\n"
	"aes_cbc_128, xrc4_128, and xrc_40.  The defaults are\n"
	"rackp_hmac_sha1, hmac_sha1, and aes_cb_128.  -Rl turns on lookup up\n"
	"names by the name and the privilege level (allowing the same name with\n"
	"different privileges and different passwords), the default is straight\n"
	"name lookup.  -Rk sets the BMC key, needed if the system does two-key\n"
	"lookups.  The -M option sets the maximum outstanding messages.\n"
	"The default is 2, ranges 1-63.\n"
	"The -H option enables certain hacks for broken platforms.  This may\n"
	"be listed multiple times to enable multiple hacks.  The currently\n"
	"available hacks are:\n"
	"  intelplus - For Intel platforms that have broken RMCP+.\n"
	"  rakp3_wrong_rolem - For systems that truncate role(m) in the RAKP3"
	" msg.\n"
	"  rmcpp_integ_sik - For systems that use SIK instead of K(1) for"
	" integrity.";
}

static ipmi_args_t *
lan_con_alloc_args(void)
{
    ipmi_args_t *args;
    lan_args_t  *largs;
    args = _ipmi_args_alloc(lan_free_args, lan_connect_args,
			    lan_args_get_val, lan_args_set_val,
			    lan_args_copy, lan_args_validate,
			    lan_args_free_val, lan_args_get_type,
			    sizeof(lan_args_t));
    if (!args)
	return NULL;

    largs = _ipmi_args_get_extra_data(args);

    /* Set defaults */
    largs->authtype = IPMI_AUTHTYPE_DEFAULT;
    largs->privilege = IPMI_PRIVILEGE_ADMIN;
    largs->conf_alg = most_secure_lanp_conf();
    largs->integ_alg = most_secure_lanp_integ();
    largs->auth_alg = most_secure_lanp_auth();
    largs->name_lookup_only = 1;
    largs->max_outstanding_msgs = DEFAULT_MAX_OUTSTANDING_MSG_COUNT;
    /* largs->hacks = IPMI_CONN_HACK_RAKP3_WRONG_ROLEM; */
    return args;
}

static ipmi_con_setup_t *lan_setup;

int
_ipmi_lan_init(os_handler_t *os_hnd)
{
    int rv;
    int i;

    rv = ipmi_create_global_lock(&lan_list_lock);
    if (rv)
	return rv;

    rv = ipmi_create_global_lock(&fd_list_lock);
    if (rv)
	return rv;
    memset(&fd_list, 0, sizeof(fd_list));
    fd_list.next = &fd_list;
    fd_list.prev = &fd_list;
    fd_list.cons_in_use = MAX_CONS_PER_FD;

#ifdef PF_INET6
    rv = ipmi_create_global_lock(&fd6_list_lock);
    if (rv)
	return rv;
    memset(&fd6_list, 0, sizeof(fd6_list));
    fd6_list.next = &fd6_list;
    fd6_list.prev = &fd6_list;
    fd6_list.cons_in_use = MAX_CONS_PER_FD;
#endif

    for (i=0; i<LAN_HASH_SIZE; i++) {
	lan_list[i].next = &(lan_list[i]);
	lan_list[i].prev = &(lan_list[i]);
	lan_list[i].lan = NULL;
	lan_ip_list[i].next = &(lan_ip_list[i]);
	lan_ip_list[i].prev = &(lan_ip_list[i]);
	lan_ip_list[i].lan = NULL;
    }

    rv = ipmi_create_global_lock(&lan_payload_lock);
    if (rv)
	return rv;

    rv = ipmi_create_global_lock(&lan_auth_lock);
    if (rv)
	return rv;

    lan_setup = _ipmi_alloc_con_setup(lan_parse_args, lan_parse_help,
				      lan_con_alloc_args);
    if (! lan_setup)
	return ENOMEM;

    rv = _ipmi_register_con_type("lan", lan_setup);
    if (rv)
	return rv;

    lan_os_hnd = os_hnd;

    return 0;
}

void
_ipmi_lan_shutdown(void)
{
    _ipmi_unregister_con_type("lan", lan_setup);
    _ipmi_free_con_setup(lan_setup);
    lan_setup = NULL;

    if (lan_list_lock) {
	ipmi_destroy_lock(lan_list_lock);
	lan_list_lock = NULL;
    }
    if (lan_payload_lock) {
	ipmi_destroy_lock(lan_payload_lock);
	lan_payload_lock = NULL;
    }
    while (oem_payload_list) {
	payload_entry_t *e = oem_payload_list;
	oem_payload_list = e->next;
	ipmi_mem_free(e);
    }
    if (lan_auth_lock) {
	ipmi_destroy_lock(lan_auth_lock);
	lan_auth_lock = NULL;
    }
    while (oem_auth_list) {
	auth_entry_t *e = oem_auth_list;
	oem_auth_list = e->next;
	ipmi_mem_free(e);
    }
    while (oem_conf_list) {
	conf_entry_t *e = oem_conf_list;
	oem_conf_list = e->next;
	ipmi_mem_free(e);
    }
    while (oem_integ_list) {
	integ_entry_t *e = oem_integ_list;
	oem_integ_list = e->next;
	ipmi_mem_free(e);
    }
    if (fd_list_lock) {
	ipmi_destroy_lock(fd_list_lock);
	fd_list_lock = NULL;
    }
    while (fd_list.next != &fd_list) {
	lan_fd_t *e = fd_list.next;
	e->next->prev = e->prev;
	e->prev->next = e->next;
	lan_os_hnd->remove_fd_to_wait_for(lan_os_hnd, e->fd_wait_id);
	close(e->fd);
	ipmi_destroy_lock(e->con_lock);
	ipmi_mem_free(e);
    }
    while (fd_free_list) {
	lan_fd_t *e = fd_free_list;
	fd_free_list = e->next;
	ipmi_destroy_lock(e->con_lock);
	ipmi_mem_free(e);
    }
#ifdef PF_INET6
    if (fd6_list_lock) {
	ipmi_destroy_lock(fd6_list_lock);
	fd6_list_lock = NULL;
    }
    while (fd6_list.next != &fd6_list) {
	lan_fd_t *e = fd6_list.next;
	e->next->prev = e->prev;
	e->prev->next = e->next;
	lan_os_hnd->remove_fd_to_wait_for(lan_os_hnd, e->fd_wait_id);
	close(e->fd);
	ipmi_destroy_lock(e->con_lock);
	ipmi_mem_free(e);
    }
    while (fd6_free_list) {
	lan_fd_t *e = fd6_free_list;
	fd6_free_list = e->next;
	ipmi_destroy_lock(e->con_lock);
	ipmi_mem_free(e);
    }
#endif
    lan_os_hnd = NULL;
}
