/*      $NetBSD: iscsi_text.c,v 1.15 2024/02/08 19:44:08 andvar Exp $   */

/*-
* Copyright (c) 2005,2006,2011 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Wasabi Systems, Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``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 FOUNDATION OR CONTRIBUTORS
* 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.
*/

#include "iscsi_globals.h"
#include "base64.h"
#include <sys/md5.h>
#include <sys/cprng.h>

#define isdigit(x) ((x) >= '0' && (x) <= '9')
#define toupper(x) ((x) & ~0x20)

/*****************************************************************************/

#define MAX_STRING   255        /* Maximum length of parameter value */
#define MAX_LIST     4          /* Maximum number of list elements we'll ever send */

/* Maximum number of negotiation parameters in the operational negotiation phase */
/* 48 should be more than enough even with the target defining its own keys */
#define MAX_NEG      48

#define CHAP_CHALLENGE_LEN    32        /* Number of bytes to send in challenge */
#define CHAP_MD5_SIZE         16        /* Number of bytes in MD5 hash */

/*****************************************************************************/

/* authentication states */

typedef enum
{
       AUTH_INITIAL,                           /* sending choice of algorithms */
       AUTH_METHOD_SELECTED,           /* received choice, sending first parameter */
       /* from here it's alg dependent */
       AUTH_CHAP_ALG_SENT,                     /* CHAP: Algorithm selected */
       AUTH_CHAP_RSP_SENT,                     /* CHAP: Response sent */
       /* for all algorithms */
       AUTH_DONE                                       /* in parameter negotiation stage */
} auth_state_t;


/* enumeration of all the keys we know, and a place for the ones we don't */

typedef enum
{
       K_AuthMethod,
       K_Auth_CHAP_Algorithm,
       K_Auth_CHAP_Challenge,
       K_Auth_CHAP_Identifier,
       K_Auth_CHAP_Name,
       K_Auth_CHAP_Response,
       K_DataDigest,
       K_DataPDUInOrder,
       K_DataSequenceInOrder,
       K_DefaultTime2Retain,
       K_DefaultTime2Wait,
       K_ErrorRecoveryLevel,
       K_FirstBurstLength,
       K_HeaderDigest,
       K_IFMarker,
       K_IFMarkInt,
       K_ImmediateData,
       K_InitialR2T,
       K_InitiatorAlias,
       K_InitiatorName,
       K_MaxBurstLength,
       K_MaxConnections,
       K_MaxOutstandingR2T,
       K_MaxRecvDataSegmentLength,
       K_OFMarker,
       K_OFMarkInt,
       K_SendTargets,
       K_SessionType,
       K_TargetAddress,
       K_TargetAlias,
       K_TargetName,
       K_TargetPortalGroupTag,
       K_NotUnderstood
} text_key_t;

/* maximum known key */
#define MAX_KEY   K_TargetPortalGroupTag

/* value types */
typedef enum
{                                               /* Value is... */
       T_NUM,                                  /* numeric */
       T_BIGNUM,                               /* large numeric */
       T_STRING,                               /* string */
       T_YESNO,                                /* boolean (Yes or No) */
       T_AUTH,                                 /* authentication type (CHAP or None for now) */
       T_DIGEST,                               /* digest (None or CRC32C) */
       T_RANGE,                                /* numeric range */
       T_SENDT,                                /* send target options (ALL, target-name, empty) */
       T_SESS                                  /* session type (Discovery or Normal) */
} val_kind_t;


/* table of negotiation key strings with value type and default */

typedef struct
{
       const uint8_t *name;                            /* the key name */
       val_kind_t val;                         /* the value type */
       uint32_t defval;                        /* default value */
} key_entry_t;

STATIC key_entry_t entries[] = {
       {"AuthMethod", T_AUTH, 0},
       {"CHAP_A", T_NUM, ISCSI_CHAP_MD5},
       {"CHAP_C", T_BIGNUM, 0},
       {"CHAP_I", T_NUM, 0},
       {"CHAP_N", T_STRING, 0},
       {"CHAP_R", T_BIGNUM, 0},
       {"DataDigest", T_DIGEST, 0},
       {"DataPDUInOrder", T_YESNO, 1},
       {"DataSequenceInOrder", T_YESNO, 1},
       {"DefaultTime2Retain", T_NUM, 20},
       {"DefaultTime2Wait", T_NUM, 2},
       {"ErrorRecoveryLevel", T_NUM, 0},
       {"FirstBurstLength", T_NUM, 64 * 1024},
       {"HeaderDigest", T_DIGEST, 0},
       {"IFMarker", T_YESNO, 0},
       {"IFMarkInt", T_RANGE, 2048},
       {"ImmediateData", T_YESNO, 1},
       {"InitialR2T", T_YESNO, 1},
       {"InitiatorAlias", T_STRING, 0},
       {"InitiatorName", T_STRING, 0},
       {"MaxBurstLength", T_NUM, 256 * 1024},
       {"MaxConnections", T_NUM, 1},
       {"MaxOutstandingR2T", T_NUM, 1},
       {"MaxRecvDataSegmentLength", T_NUM, 8192},
       {"OFMarker", T_YESNO, 0},
       {"OFMarkInt", T_RANGE, 2048},
       {"SendTargets", T_SENDT, 0},
       {"SessionType", T_SESS, 0},
       {"TargetAddress", T_STRING, 0},
       {"TargetAlias", T_STRING, 0},
       {"TargetName", T_STRING, 0},
       {"TargetPortalGroupTag", T_NUM, 0},
       {NULL, T_STRING, 0}
};

/* a negotiation parameter: key and values (there may be more than 1 for lists) */
typedef struct
{
       text_key_t key;         /* the key */
       int list_num;           /* number of elements in list, doubles as */
                               /* data size for large numeric values */
       bool hex_bignums;       /* whether to encode in hex or base64 */
       union
       {
               uint32_t nval[MAX_LIST];/* numeric or enumeration values */
               uint8_t *sval;          /* string or data pointer */
       } val;
} negotiation_parameter_t;


/* Negotiation state flags */
#define NS_SENT      0x01               /* key was sent to target */
#define NS_RECEIVED  0x02               /* key was received from target */

typedef struct
{
       negotiation_parameter_t pars[MAX_NEG];  /* the parameters to send */
       negotiation_parameter_t *cpar;                  /* the last parameter set */
       uint16_t num_pars;                                              /* number of parameters to send */
       auth_state_t auth_state;                                /* authentication state */
       iscsi_auth_types_t auth_alg;                    /* authentication algorithm */
       uint8_t kflags[MAX_KEY + 2];                    /* negotiation flags for each key */
       uint8_t password[MAX_STRING + 1];               /* authentication secret */
       uint8_t target_password[MAX_STRING + 1];        /* target authentication secret */
       uint8_t user_name[MAX_STRING + 1];              /* authentication user ID */
       uint8_t temp_buf[MAX_STRING + 1];               /* scratch buffer */

       bool HeaderDigest;
       bool DataDigest;
       bool InitialR2T;
       bool ImmediateData;
       uint32_t ErrorRecoveryLevel;
       uint32_t MaxRecvDataSegmentLength;
       uint32_t MaxConnections;
       uint32_t DefaultTime2Wait;
       uint32_t DefaultTime2Retain;
       uint32_t MaxBurstLength;
       uint32_t FirstBurstLength;
       uint32_t MaxOutstandingR2T;

} negotiation_state_t;


#define TX(state, key) (state->kflags [key] & NS_SENT)
#define RX(state, key) (state->kflags [key] & NS_RECEIVED)

/*****************************************************************************/

STATIC void
chap_md5_response(uint8_t *buffer, uint8_t identifier, uint8_t *secret,
                                 uint8_t *challenge, int challenge_size)
{
       MD5_CTX md5;

       MD5Init(&md5);
       MD5Update(&md5, &identifier, 1);
       MD5Update(&md5, secret, strlen(secret));
       MD5Update(&md5, challenge, challenge_size);
       MD5Final(buffer, &md5);
}

/*****************************************************************************/

/*
* hexdig:
*    Return value of hex digit.
*    Note: a null character is acceptable, and returns 0.
*
*    Parameter:
*          c     The character
*
*    Returns:    The value, -1 on error.
*/

static __inline int
hexdig(uint8_t c)
{

       if (!c) {
               return 0;
       }
       if (isdigit(c)) {
               return c - '0';
       }
       c = toupper(c);
       if (c >= 'A' && c <= 'F') {
               return c - 'A' + 10;
       }
       return -1;
}

/*
* skiptozero:
*    Skip to next zero character in buffer.
*
*    Parameter:
*          buf      The buffer pointer
*
*    Returns:    The pointer to the character after the zero character.
*/

static __inline uint8_t *
skiptozero(uint8_t *buf)
{

       while (*buf) {
               buf++;
       }
       return buf + 1;
}


/*
* get_bignumval:
*    Get a large numeric value.
*    NOTE: Overwrites source string.
*
*    Parameter:
*          buf      The buffer pointer
*          par      The parameter
*
*    Returns:    The pointer to the next parameter, NULL on error.
*/

STATIC uint8_t *
get_bignumval(uint8_t *buf, negotiation_parameter_t *par)
{
       int val;
       char c;
       uint8_t *dp = buf;

       par->val.sval = buf;

       if (buf[0] == '0' && (buf[1] == 'x' || buf[1] == 'X')) {
               buf += 2;
               while ((c = *buf) != 0x0) {
                       buf++;
                       val = (hexdig(c) << 4) | hexdig(*buf);
                       if (val < 0) {
                               return NULL;
                       }
                       *dp++ = (uint8_t) val;
                       if (*buf) {
                               buf++;
                       }
               }
               buf++;
               par->list_num = dp - par->val.sval;
               par->hex_bignums = true;
       } else if (buf[0] == '0' && (buf[1] == 'b' || buf[1] == 'B')) {
               buf = base64_decode(&buf[2], par->val.sval, &par->list_num);
       } else {
               DEBOUT(("Ill-formatted large number <%s>\n", buf));
               return NULL;
       }

       return buf;
}


/*
* get_numval:
*    Get a numeric value.
*
*    Parameter:
*          buf      The buffer pointer
*          pval     The pointer to the result.
*          sep      Separator to next value.
*
*    Returns:    The pointer to the next parameter, NULL on error.
*/

STATIC uint8_t *
get_numval(uint8_t *buf, uint32_t *pval, const uint8_t sep)
{
       uint32_t val = 0;
       char c;

       if (buf[0] == '0' && (buf[1] == 'x' || buf[1] == 'X')) {
               buf += 2;
               while (*buf && *buf != '~') {
                       int n;

                       if ((n = hexdig(*buf++)) < 0)
                               return NULL;
                       val = (val << 4) | n;
               }
       } else
               while (*buf && *buf != '~') {
                       c = *buf++;
                       if (!isdigit(c))
                               return NULL;
                       val = val * 10 + (c - '0');
               }

       *pval = val;

       return buf + 1;
}


/*
* get_range:
*    Get a numeric range.
*
*    Parameter:
*          buf      The buffer pointer
*          pval1    The pointer to the first result.
*          pval2    The pointer to the second result.
*
*    Returns:    The pointer to the next parameter, NULL on error.
*/

STATIC uint8_t *
get_range(uint8_t *buf, uint32_t *pval1, uint32_t *pval2)
{

       if ((buf = get_numval(buf, pval1, '~')) == NULL)
               return NULL;
       if (!*buf)
               return NULL;
       if ((buf = get_numval(buf, pval2, '~')) == NULL)
               return NULL;
       return buf;
}


/*
* get_ynval:
*    Get a yes/no selection.
*
*    Parameter:
*          buf      The buffer pointer
*          pval     The pointer to the result.
*
*    Returns:    The pointer to the next parameter, NULL on error.
*/

STATIC uint8_t *
get_ynval(uint8_t *buf, uint32_t *pval)
{

       if (strcmp(buf, "Yes") == 0)
               *pval = 1;
       else if (strcmp(buf, "No") == 0)
               *pval = 0;
       else
               return NULL;

       return skiptozero(buf);
}


/*
* get_digestval:
*    Get a digest selection.
*
*    Parameter:
*          buf      The buffer pointer
*          pval     The pointer to the result.
*
*    Returns:    The pointer to the next parameter, NULL on error.
*/

STATIC uint8_t *
get_digestval(uint8_t *buf, uint32_t *pval)
{

       if (strcmp(buf, "CRC32C") == 0)
               *pval = 1;
       else if (strcmp(buf, "None") == 0)
               *pval = 0;
       else
               return NULL;

       return skiptozero(buf);
}


/*
* get_authval:
*    Get an authentication method.
*
*    Parameter:
*          buf      The buffer pointer
*          pval     The pointer to the result.
*
*    Returns:    The pointer to the next parameter, NULL on error.
*/

STATIC uint8_t *
get_authval(uint8_t *buf, uint32_t *pval)
{

       if (strcmp(buf, "None") == 0)
               *pval = ISCSI_AUTH_None;
       else if (strcmp(buf, "CHAP") == 0)
               *pval = ISCSI_AUTH_CHAP;
       else if (strcmp(buf, "KRB5") == 0)
               *pval = ISCSI_AUTH_KRB5;
       else if (strcmp(buf, "SRP") == 0)
               *pval = ISCSI_AUTH_SRP;
       else
               return NULL;

       return skiptozero(buf);
}


/*
* get_strval:
*    Get a string value (returns pointer to original buffer, not a copy).
*
*    Parameter:
*          buf      The buffer pointer
*          pval     The pointer to the result pointer.
*
*    Returns:    The pointer to the next parameter, NULL on error.
*/

STATIC uint8_t *
get_strval(uint8_t *buf, uint8_t **pval)
{

       if (strlen(buf) > MAX_STRING)
               return NULL;

       *pval = buf;

       return skiptozero(buf);
}


/*
* get_parameter:
*    Analyze a key=value string.
*    NOTE: The string is modified in the process.
*
*    Parameter:
*          buf      The buffer pointer
*          par      The parameter descriptor to be filled in
*
*    Returns:    The pointer to the next parameter, NULL on error.
*/

STATIC uint8_t *
get_parameter(uint8_t *buf, negotiation_parameter_t *par)
{
       uint8_t *bp = buf;
       int i;

       while (*bp && *bp != '=') {
               bp++;
       }
       if (!*bp) {
               DEBOUT(("get_parameter: Premature end of parameter\n"));
               return NULL;
       }

       *bp++ = 0;

       for (i = 0; i <= MAX_KEY; i++)
               if (!strcmp(buf, entries[i].name))
                       break;

       par->key = i;
       par->list_num = 1;
       par->hex_bignums = false; /* set by get_bignumval */

       if (i > MAX_KEY) {
               DEBOUT(("get_parameter: unrecognized key <%s>\n", buf));
               if (strlen(buf) > MAX_STRING) {
                       DEBOUT(("get_parameter: key name > MAX_STRING\n"));
                       return NULL;
               }
               par->val.sval = buf;
               return skiptozero(bp);
       }

       DEB(10, ("get_par: key <%s>=%d, val=%d, ret %p\n",
                       buf, i, entries[i].val, bp));
       DEB(10, ("get_par: value '%s'\n",bp));

       switch (entries[i].val) {
       case T_NUM:
               bp = get_numval(bp, &par->val.nval[0], '\0');
               break;

       case T_BIGNUM:
               bp = get_bignumval(bp, par);
               break;

       case T_STRING:
               bp = get_strval(bp, &par->val.sval);
               break;

       case T_YESNO:
               bp = get_ynval(bp, &par->val.nval[0]);
               break;

       case T_AUTH:
               bp = get_authval(bp, &par->val.nval[0]);
               break;

       case T_DIGEST:
               bp = get_digestval(bp, &par->val.nval[0]);
               break;

       case T_RANGE:
               bp = get_range(bp, &par->val.nval[0], &par->val.nval[1]);
               break;

       default:
               /* Target sending any other types is wrong */
               bp = NULL;
               break;
       }
       return bp;
}

/*****************************************************************************/

/*
* my_strcpy:
*    Replacement for strcpy that returns the end of the result string
*
*    Parameter:
*          dest     The destination buffer pointer
*          src      The source string
*
*    Returns:    A pointer to the terminating zero of the result.
*/

static __inline unsigned
my_strcpy(uint8_t *dest, const uint8_t *src)
{
       unsigned        cc;

       for (cc = 0 ; (*dest = *src) != 0x0 ; cc++) {
               dest++;
               src++;
       }
       return cc;
}

/*
* put_bignumval:
*    Write a large numeric value.
*    NOTE: Overwrites source string.
*
*    Parameter:
*          buf      The buffer pointer
*          par      The parameter
*
*    Returns:    The pointer to the next parameter, NULL on error.
*/

STATIC unsigned
put_bignumval(negotiation_parameter_t *par, uint8_t *buf)
{
       int k, c;

       if (par->hex_bignums) {
               my_strcpy(buf, "0x");
               for (k=0; k<par->list_num; ++k) {
                       c = par->val.sval[k] >> 4;
                       buf[2+2*k] = c < 10 ? '0' + c : 'a' + (c-10);
                       c = par->val.sval[k] & 0xf;
                       buf[2+2*k+1] = c < 10 ? '0' + c : 'a' + (c-10);
               }
               buf[2+2*k] = '\0';

               return 2+2*par->list_num;
       }
       return base64_encode(par->val.sval, par->list_num, buf);
}

/*
* put_parameter:
*    Create a key=value string.
*
*    Parameter:
*          buf      The buffer pointer
*          par      The parameter descriptor
*
*    Returns:    The pointer to the next free buffer space, NULL on error.
*/

STATIC unsigned
put_parameter(uint8_t *buf, unsigned len, negotiation_parameter_t *par)
{
       int i;
       unsigned        cc, cl;
       const uint8_t *sp;

       DEB(10, ("put_par: key <%s>=%d, val=%d\n",
               entries[par->key].name, par->key, entries[par->key].val));

       if (par->key > MAX_KEY) {
               return snprintf(buf, len, "%s=NotUnderstood", par->val.sval);
       }

       cc = snprintf(buf, len, "%s=", entries[par->key].name);
       if (cc >= len)
               return len;

       for (i = 0; i < par->list_num; i++) {
               switch (entries[par->key].val) {
               case T_NUM:
                       cl = snprintf(&buf[cc], len - cc, "%d",
                                      par->val.nval[i]);
                       break;

               case T_BIGNUM:
                       cl = put_bignumval(par, &buf[cc]);
                       i = par->list_num;
                       break;

               case T_STRING:
                       cl =  my_strcpy(&buf[cc], par->val.sval);
                       break;

               case T_YESNO:
                       cl = my_strcpy(&buf[cc],
                               (par->val.nval[i]) ? "Yes" : "No");
                       break;

               case T_AUTH:
                       switch (par->val.nval[i]) {
                       case ISCSI_AUTH_CHAP:
                               sp = "CHAP";
                               break;
                       case ISCSI_AUTH_KRB5:
                               sp = "KRB5";
                               break;
                       case ISCSI_AUTH_SRP:
                               sp = "SRP";
                               break;
                       default:
                               sp = "None";
                               break;
                       }
                       cl = my_strcpy(&buf[cc], sp);
                       break;

               case T_DIGEST:
                       cl = my_strcpy(&buf[cc],
                               (par->val.nval[i]) ? "CRC32C" : "None");
                       break;

               case T_RANGE:
                       if ((i + 1) >= par->list_num) {
                               cl = my_strcpy(&buf[cc], "Reject");
                       } else {
                               cl = snprintf(&buf[cc], len - cc,
                                               "%d~%d", par->val.nval[i],
                                               par->val.nval[i + 1]);
                               i++;
                       }
                       break;

               case T_SENDT:
                       cl = my_strcpy(&buf[cc], par->val.sval);
                       break;

               case T_SESS:
                       cl = my_strcpy(&buf[cc],
                               (par->val.nval[i]) ? "Normal" : "Discovery");
                       break;

               default:
                       cl = 0;
                       /* We shouldn't be here... */
                       DEBOUT(("Invalid type %d in put_parameter!\n",
                                       entries[par->key].val));
                       break;
               }

               DEB(10, ("put_par: value '%s'\n",&buf[cc]));

               cc += cl;
               if (cc >= len)
                       return len;
               if ((i + 1) < par->list_num) {
                       if (cc >= len)
                               return len;
                       buf[cc++] = ',';
               }
       }

       if (cc >= len)
               return len;
       buf[cc] = 0x0;                          /* make sure it's terminated */
       return cc + 1;                          /* return next place in list */
}


/*
* put_par_block:
*    Fill a parameter block
*
*    Parameter:
*          buf      The buffer pointer
*          pars     The parameter descriptor array
*          n        The number of elements
*
*    Returns:    result from put_parameter (ptr to buffer, NULL on error)
*/

static __inline unsigned
put_par_block(uint8_t *buf, unsigned len, negotiation_parameter_t *pars, int n)
{
       unsigned        cc;
       int i;

       for (cc = 0, i = 0; i < n; i++) {
               cc += put_parameter(&buf[cc], len - cc, pars++);
               if (cc >= len) {
                       break;
               }
       }
       return cc;
}

/*
* parameter_size:
*    Determine the size of a key=value string.
*
*    Parameter:
*          par      The parameter descriptor
*
*    Returns:    The size of the resulting string.
*/

STATIC int
parameter_size(negotiation_parameter_t *par)
{
       int i, size;
       char buf[24];   /* max. 2 10-digit numbers + sep. */

       if (par->key > MAX_KEY) {
               return strlen(par->val.sval) + 15;
       }
       /* count '=' and terminal zero */
       size = strlen(entries[par->key].name) + 2;

       for (i = 0; i < par->list_num; i++) {
               switch (entries[par->key].val) {
               case T_NUM:
                       size += snprintf(buf, sizeof(buf), "%d",
                                       par->val.nval[i]);
                       break;

               case T_BIGNUM:
                       /* list_num holds value size */
                       if (par->hex_bignums)
                               size += 2 + 2*par->list_num;
                       else
                               size += base64_enclen(par->list_num);
                       i = par->list_num;
                       break;

               case T_STRING:
               case T_SENDT:
                       size += strlen(par->val.sval);
                       break;

               case T_YESNO:
                       size += (par->val.nval[i]) ? 3 : 2;
                       break;

               case T_AUTH:
                       size += (par->val.nval[i] == ISCSI_AUTH_SRP) ? 3 : 4;
                       break;

               case T_DIGEST:
                       size += (par->val.nval[i]) ? 6 : 4;
                       break;

               case T_RANGE:
                       if (i+1 < par->list_num) {
                               size += snprintf(buf, sizeof(buf), "%d~%d",
                                       par->val.nval[i],
                                       par->val.nval[i + 1]);
                               i++;
                       } else
                               DEBOUT(("Incomplete range parameter\n"));
                       break;

               case T_SESS:
                       size += (par->val.nval[i]) ? 6 : 9;
                       break;

               default:
                       /* We shouldn't be here... */
                       DEBOUT(("Invalid type %d in parameter_size!\n",
                                       entries[par->key].val));
                       break;
               }
               if ((i + 1) < par->list_num) {
                       size++;
               }
       }

       return size;
}


/*
* total_size:
*    Determine the size of a negotiation data block
*
*    Parameter:
*          pars     The parameter descriptor array
*          n        The number of elements
*
*    Returns:    The size of the block
*/

static __inline int
total_size(negotiation_parameter_t *pars, int n)
{
       int i, size;

       for (i = 0, size = 0; i < n; i++) {
               size += parameter_size(pars++);
       }
       return size;
}

/*****************************************************************************/


/*
* complete_pars:
*    Allocate space for text parameters, translate parameter values into
*    text.
*
*    Parameter:
*          state    Negotiation state
*          pdu      The transmit PDU
*
*    Returns:    0     On success
*                > 0   (an ISCSI error code) if an error occurred.
*/

STATIC int
complete_pars(negotiation_state_t *state, pdu_t *pdu)
{
       int len;
       uint8_t *bp;

       len = total_size(state->pars, state->num_pars);

       DEB(10, ("complete_pars: n=%d, len=%d\n", state->num_pars, len));

       if (len == 0) {
               pdu->pdu_temp_data = NULL;
               pdu->pdu_temp_data_len = 0;
               return 0;
       }

       if ((bp = malloc(len, M_TEMP, M_WAITOK)) == NULL) {
               DEBOUT(("*** Out of memory in complete_pars\n"));
               return ISCSI_STATUS_NO_RESOURCES;
       }
       pdu->pdu_temp_data = bp;

       if (put_par_block(pdu->pdu_temp_data, len, state->pars,
                       state->num_pars) == 0) {
               DEBOUT(("Bad parameter in complete_pars\n"));
               return ISCSI_STATUS_PARAMETER_INVALID;
       }

       pdu->pdu_temp_data_len = len;
       return 0;
}


/*
* set_key_n:
*    Initialize a key and its numeric value.
*
*    Parameter:
*          state    Negotiation state
*          key      The key
*          val      The value
*/

STATIC negotiation_parameter_t *
set_key_n(negotiation_state_t *state, text_key_t key, uint32_t val)
{
       negotiation_parameter_t *par;

       if (state->num_pars >= MAX_NEG) {
               DEBOUT(("set_key_n: num_pars (%d) >= MAX_NEG (%d)\n",
                               state->num_pars, MAX_NEG));
               return NULL;
       }
       par = &state->pars[state->num_pars];
       par->key = key;
       par->list_num = 1;
       par->val.nval[0] = val;
       state->num_pars++;
       state->kflags[key] |= NS_SENT;

       return par;
}

/*
* set_key_s:
*    Initialize a key and its string value.
*
*    Parameter:
*          state    Negotiation state
*          key      The key
*          val      The value
*/

STATIC negotiation_parameter_t *
set_key_s(negotiation_state_t *state, text_key_t key, uint8_t *val)
{
       negotiation_parameter_t *par;

       if (state->num_pars >= MAX_NEG) {
               DEBOUT(("set_key_s: num_pars (%d) >= MAX_NEG (%d)\n",
                               state->num_pars, MAX_NEG));
               return NULL;
       }
       par = &state->pars[state->num_pars];
       par->key = key;
       par->list_num = 1;
       par->val.sval = val;
       par->hex_bignums = iscsi_hex_bignums;
       state->num_pars++;
       state->kflags[key] |= NS_SENT;

       return par;
}


/*****************************************************************************/

/*
* eval_parameter:
*    Evaluate a received negotiation value.
*
*    Parameter:
*          conn     The connection
*          state    The negotiation state
*          par      The parameter
*
*    Returns:    0 on success, else an ISCSI status value.
*/

STATIC int
eval_parameter(connection_t *conn, negotiation_state_t *state,
                          negotiation_parameter_t *par)
{
       uint32_t n = par->val.nval[0];
       size_t sz;
       text_key_t key = par->key;
       bool sent = (state->kflags[key] & NS_SENT) != 0;

       state->kflags[key] |= NS_RECEIVED;

       switch (key) {
               /*
                *  keys connected to security negotiation
                */
       case K_AuthMethod:
               if (n) {
                       DEBOUT(("eval_par: AuthMethod nonzero (%d)\n", n));
                       return ISCSI_STATUS_NEGOTIATION_ERROR;
               }
               break;

       case K_Auth_CHAP_Algorithm:
       case K_Auth_CHAP_Challenge:
       case K_Auth_CHAP_Identifier:
       case K_Auth_CHAP_Name:
       case K_Auth_CHAP_Response:
               DEBOUT(("eval_par: Authorization Key in Operational Phase\n"));
               return ISCSI_STATUS_NEGOTIATION_ERROR;

               /*
                * keys we always send
                */
       case K_DataDigest:
               state->DataDigest = n;
               if (!sent)
                       set_key_n(state, key, n);
               break;

       case K_HeaderDigest:
               state->HeaderDigest = n;
               if (!sent)
                       set_key_n(state, key, n);
               break;

       case K_ErrorRecoveryLevel:
               state->ErrorRecoveryLevel = n;
               if (!sent)
                       set_key_n(state, key, n);
               break;

       case K_ImmediateData:
               state->ImmediateData = n;
               if (!sent)
                       set_key_n(state, key, n);
               break;

       case K_InitialR2T:
               state->InitialR2T = n;
               if (!sent)
                       set_key_n(state, key, n);
               break;

       case K_MaxRecvDataSegmentLength:
               state->MaxRecvDataSegmentLength = n;
               /* this is basically declarative, not negotiated */
               /* (each side has its own value) */
               break;

               /*
                * keys we don't always send, so we may have to reflect the value
                */
       case K_DefaultTime2Retain:
               state->DefaultTime2Retain = n = min(state->DefaultTime2Retain, n);
               if (!sent)
                       set_key_n(state, key, n);
               break;

       case K_DefaultTime2Wait:
               state->DefaultTime2Wait = n = min(state->DefaultTime2Wait, n);
               if (!sent)
                       set_key_n(state, key, n);
               break;

       case K_MaxConnections:
               if (state->MaxConnections)
                       state->MaxConnections = n = min(state->MaxConnections, n);
               else
                       state->MaxConnections = n;

               if (!sent)
                       set_key_n(state, key, n);
               break;

       case K_MaxOutstandingR2T:
               state->MaxOutstandingR2T = n;
               if (!sent)
                       set_key_n(state, key, n);
               break;

       case K_FirstBurstLength:
               state->FirstBurstLength = n;
               if (!sent)
                       set_key_n(state, key, n);
               break;

       case K_MaxBurstLength:
               state->MaxBurstLength = n;
               if (!sent)
                       set_key_n(state, key, n);
               break;

       case K_IFMarker:
       case K_OFMarker:
               /* not (yet) supported */
               if (!sent)
                       set_key_n(state, key, 0);
               break;

       case K_IFMarkInt:
       case K_OFMarkInt:
               /* it's a range, and list_num will be 1, so this will reply "Reject" */
               if (!sent)
                       set_key_n(state, key, 0);
               break;

       case K_DataPDUInOrder:
       case K_DataSequenceInOrder:
               /* values are don't care */
               if (!sent)
                       set_key_n(state, key, n);
               break;

       case K_NotUnderstood:
               /* return "NotUnderstood" */
               set_key_s(state, key, par->val.sval);
               break;

               /*
                * Declarative keys (no response required)
                */
       case K_TargetAddress:
               /* ignore for now... */
               break;

       case K_TargetAlias:
               if (conn->c_login_par->is_present.TargetAlias) {
                       copyoutstr(par->val.sval, conn->c_login_par->TargetAlias,
                               ISCSI_STRING_LENGTH - 1, &sz);
                       /* do anything with return code?? */
               }
               break;

       case K_TargetPortalGroupTag:
               /* ignore for now... */
               break;

       default:
               DEBOUT(("eval_par: Invalid parameter type %d\n", par->key));
               return ISCSI_STATUS_NEGOTIATION_ERROR;
       }
       return 0;
}

/*****************************************************************************/


/*
* init_session_parameters:
*    Initialize session-related negotiation parameters from existing session
*
*    Parameter:
*          sess     The session
*          state    The negotiation state
*/

STATIC void
init_session_parameters(session_t *sess, negotiation_state_t *state)
{

       state->ErrorRecoveryLevel = sess->s_ErrorRecoveryLevel;
       state->InitialR2T = sess->s_InitialR2T;
       state->ImmediateData = sess->s_ImmediateData;
       state->MaxConnections = sess->s_MaxConnections;
       state->DefaultTime2Wait = sess->s_DefaultTime2Wait;
       state->DefaultTime2Retain = sess->s_DefaultTime2Retain;
       state->MaxBurstLength = sess->s_MaxBurstLength;
       state->FirstBurstLength = sess->s_FirstBurstLength;
       state->MaxOutstandingR2T = sess->s_MaxOutstandingR2T;
}



/*
* assemble_login_parameters:
*    Assemble the initial login negotiation parameters.
*
*    Parameter:
*          conn     The connection
*          ccb      The CCB for the login exchange
*          pdu      The PDU to use for sending
*
*    Returns:    < 0   if more security negotiation is required
*                0     if this is the last security negotiation block
*                > 0   (an ISCSI error code) if an error occurred.
*/

int
assemble_login_parameters(connection_t *conn, ccb_t *ccb, pdu_t *pdu)
{
       iscsi_login_parameters_t *par = conn->c_login_par;
       size_t sz;
       int rc, i, next;
       negotiation_state_t *state;
       negotiation_parameter_t *cpar;

       state = malloc(sizeof(*state), M_TEMP, M_WAITOK | M_ZERO);
       if (state == NULL) {
               DEBOUT(("*** Out of memory in assemble_login_params\n"));
               return ISCSI_STATUS_NO_RESOURCES;
       }
       ccb->ccb_temp_data = state;

       if (!iscsi_InitiatorName[0]) {
               DEBOUT(("No InitiatorName\n"));
               return ISCSI_STATUS_PARAMETER_MISSING;
       }
       set_key_s(state, K_InitiatorName, iscsi_InitiatorName);

       if (iscsi_InitiatorAlias[0])
               set_key_s(state, K_InitiatorAlias, iscsi_InitiatorAlias);

       conn->c_Our_MaxRecvDataSegmentLength =
               (par->is_present.MaxRecvDataSegmentLength)
               ? par->MaxRecvDataSegmentLength : DEFAULT_MaxRecvDataSegmentLength;

       /* setup some values for authentication */
       if (par->is_present.password)
               copyinstr(par->password, state->password, MAX_STRING, &sz);
       if (par->is_present.target_password)
               copyinstr(par->target_password, state->target_password,
                       MAX_STRING, &sz);
       if (par->is_present.user_name)
               copyinstr(par->user_name, state->user_name, MAX_STRING, &sz);
       else
               strlcpy(state->user_name, iscsi_InitiatorName,
                       sizeof(state->user_name));

       next = TRUE;

       set_key_n(state, K_SessionType,
                         par->login_type > ISCSI_LOGINTYPE_DISCOVERY);

       cpar = set_key_n(state, K_AuthMethod, ISCSI_AUTH_None);

       if (cpar != NULL && par->is_present.auth_info &&
               par->auth_info.auth_number > 0) {
               if (par->auth_info.auth_number > ISCSI_AUTH_OPTIONS) {
                       DEBOUT(("Auth number too big in asm_login\n"));
                       return ISCSI_STATUS_PARAMETER_INVALID;
               }
               cpar->list_num = par->auth_info.auth_number;
               for (i = 0; i < cpar->list_num; i++) {
                       cpar->val.nval[i] = par->auth_info.auth_type[i];
                       if (par->auth_info.auth_type[i])
                               next = FALSE;
               }
       }

       if (par->is_present.TargetName)
               copyinstr(par->TargetName, state->temp_buf, ISCSI_STRING_LENGTH - 1,
                                 &sz);
       else {
               state->temp_buf[0] = 0;
               sz = 0;
       }

       if ((!sz || !state->temp_buf[0]) &&
               par->login_type != ISCSI_LOGINTYPE_DISCOVERY) {
               DEBOUT(("No TargetName\n"));
               return ISCSI_STATUS_PARAMETER_MISSING;
       }

       if (state->temp_buf[0]) {
               set_key_s(state, K_TargetName, state->temp_buf);
       }

       if ((rc = complete_pars(state, pdu)) != 0)
               return rc;

       return (next) ? 0 : -1;
}

/*
* assemble_security_parameters:
*    Assemble the security negotiation parameters.
*
*    Parameter:
*          conn     The connection
*          rx_pdu   The received login response PDU
*          tx_pdu   The transmit PDU
*
*    Returns:    < 0   if more security negotiation is required
*                0     if this is the last security negotiation block
*                > 0   (an ISCSI error code) if an error occurred.
*/

int
assemble_security_parameters(connection_t *conn, ccb_t *ccb, pdu_t *rx_pdu,
                                                        pdu_t *tx_pdu)
{
       negotiation_state_t *state = (negotiation_state_t *) ccb->ccb_temp_data;
       iscsi_login_parameters_t *par = conn->c_login_par;
       negotiation_parameter_t rxp, *cpar;
       uint8_t *rxpars;
       int rc, next;
       uint8_t identifier = 0;
       uint8_t *challenge = NULL;
       int challenge_size = 0;
       uint8_t *response = NULL;
       int response_size = 0;
       bool challenge_hex = iscsi_hex_bignums;

       state->num_pars = 0;
       next = 0;

       rxpars = (uint8_t *) rx_pdu->pdu_temp_data;
       if (rxpars == NULL) {
               DEBOUT(("No received parameters!\n"));
               return ISCSI_STATUS_NEGOTIATION_ERROR;
       }
       /* Note: There are always at least 2 extra bytes past temp_data_len */
       rxpars[rx_pdu->pdu_temp_data_len] = '\0';
       rxpars[rx_pdu->pdu_temp_data_len + 1] = '\0';

       while (*rxpars) {
               if ((rxpars = get_parameter(rxpars, &rxp)) == NULL) {
                       DEBOUT(("get_parameter returned error\n"));
                       return ISCSI_STATUS_NEGOTIATION_ERROR;
               }

               state->kflags[rxp.key] |= NS_RECEIVED;

               switch (rxp.key) {
               case K_AuthMethod:
                       if (state->auth_state != AUTH_INITIAL) {
                               DEBOUT(("AuthMethod received, auth_state = %d\n",
                                               state->auth_state));
                               return ISCSI_STATUS_NEGOTIATION_ERROR;
                       }

                       /* Note: if the selection is None, we shouldn't be here,
                        * the target should have transited the state to op-neg.
                        */
                       if (rxp.val.nval[0] != ISCSI_AUTH_CHAP) {
                               DEBOUT(("AuthMethod isn't CHAP (%d)\n", rxp.val.nval[0]));
                               return ISCSI_STATUS_NEGOTIATION_ERROR;
                       }

                       state->auth_state = AUTH_METHOD_SELECTED;
                       state->auth_alg = rxp.val.nval[0];
                       break;

               case K_Auth_CHAP_Algorithm:
                       if (state->auth_state != AUTH_CHAP_ALG_SENT ||
                           rxp.val.nval[0] != ISCSI_CHAP_MD5) {
                               DEBOUT(("Bad algorithm, auth_state = %d, alg %d\n",
                                               state->auth_state, rxp.val.nval[0]));
                               return ISCSI_STATUS_NEGOTIATION_ERROR;
                       }
                       break;

               case K_Auth_CHAP_Challenge:
                       if (state->auth_state != AUTH_CHAP_ALG_SENT || !rxp.list_num) {
                               DEBOUT(("Bad Challenge, auth_state = %d, len %d\n",
                                               state->auth_state, rxp.list_num));
                               return ISCSI_STATUS_NEGOTIATION_ERROR;
                       }
                       challenge = rxp.val.sval;
                       challenge_size = rxp.list_num;
                       /* respond in the same format as the challenge */
                       challenge_hex = rxp.hex_bignums;
                       break;

               case K_Auth_CHAP_Identifier:
                       if (state->auth_state != AUTH_CHAP_ALG_SENT) {
                               DEBOUT(("Bad ID, auth_state = %d, id %d\n",
                                               state->auth_state, rxp.val.nval[0]));
                               return ISCSI_STATUS_NEGOTIATION_ERROR;
                       }
                       identifier = (uint8_t) rxp.val.nval[0];
                       break;

               case K_Auth_CHAP_Name:
                       if (state->auth_state != AUTH_CHAP_RSP_SENT) {
                               DEBOUT(("Bad Name, auth_state = %d, name <%s>\n",
                                               state->auth_state, rxp.val.sval));
                               return ISCSI_STATUS_NEGOTIATION_ERROR;
                       }
                       /* what do we do with the name?? */
                       break;

               case K_Auth_CHAP_Response:
                       if (state->auth_state != AUTH_CHAP_RSP_SENT) {
                               DEBOUT(("Bad Response, auth_state = %d, size %d\n",
                                               state->auth_state, rxp.list_num));
                               return ISCSI_STATUS_NEGOTIATION_ERROR;
                       }
                       response = rxp.val.sval;
                       response_size = rxp.list_num;
                       if (response_size != CHAP_MD5_SIZE) {
                               DEBOUT(("CHAP Response, bad size %d\n",
                                               response_size));
                               return ISCSI_STATUS_NEGOTIATION_ERROR;
                       }
                       break;

               default:
                       rc = eval_parameter(conn, state, &rxp);
                       if (rc)
                               return rc;
                       break;
               }
       }

       switch (state->auth_state) {
       case AUTH_INITIAL:
               DEBOUT(("Didn't receive Method\n"));
               return ISCSI_STATUS_NEGOTIATION_ERROR;

       case AUTH_METHOD_SELECTED:
               set_key_n(state, K_Auth_CHAP_Algorithm, ISCSI_CHAP_MD5);
               state->auth_state = AUTH_CHAP_ALG_SENT;
               next = -1;
               break;

       case AUTH_CHAP_ALG_SENT:
               if (!RX(state, K_Auth_CHAP_Algorithm) ||
                       !RX(state, K_Auth_CHAP_Identifier) ||
                       !RX(state, K_Auth_CHAP_Challenge)) {
                       DEBOUT(("Didn't receive all parameters\n"));
                       return ISCSI_STATUS_NEGOTIATION_ERROR;
               }

               set_key_s(state, K_Auth_CHAP_Name, state->user_name);

               chap_md5_response(state->temp_buf, identifier,
                   state->password, challenge, challenge_size);

               cpar = set_key_s(state, K_Auth_CHAP_Response, state->temp_buf);
               if (cpar != NULL) {
                       cpar->list_num = CHAP_MD5_SIZE;
                       /* respond in same format as challenge */
                       cpar->hex_bignums = challenge_hex;
               }

               if (par->auth_info.mutual_auth) {
                       if (!state->target_password[0]) {
                               DEBOUT(("No target password with mutual authentication!\n"));
                               return ISCSI_STATUS_PARAMETER_MISSING;
                       }

                       cprng_strong(kern_cprng,
                                    &state->temp_buf[CHAP_MD5_SIZE],
                                    CHAP_CHALLENGE_LEN + 1, 0);
                       set_key_n(state, K_Auth_CHAP_Identifier,
                                         state->temp_buf[CHAP_MD5_SIZE]);
                       cpar = set_key_s(state, K_Auth_CHAP_Challenge,
                                                        &state->temp_buf[CHAP_MD5_SIZE + 1]);
                       if (cpar != NULL) {
                               cpar->list_num = CHAP_CHALLENGE_LEN;
                               /* use same format as target challenge */
                               cpar->hex_bignums = challenge_hex;
                       }

                       /* transitional state */
                       conn->c_state = ST_SEC_FIN;
               }
               state->auth_state = AUTH_CHAP_RSP_SENT;
               break;

       case AUTH_CHAP_RSP_SENT:
               /* we can only be here for mutual authentication */
               if (!par->auth_info.mutual_auth || response == NULL) {
                       DEBOUT(("Mutual authentication not requested\n"));
                       return ISCSI_STATUS_NEGOTIATION_ERROR;
               }

               chap_md5_response(state->temp_buf,
                       state->temp_buf[CHAP_MD5_SIZE],
                       state->target_password,
                       &state->temp_buf[CHAP_MD5_SIZE + 1],
                       CHAP_CHALLENGE_LEN);

               if (response_size > sizeof(state->temp_buf) ||
                   memcmp(state->temp_buf, response, response_size)) {
                       DEBOUT(("Mutual authentication mismatch\n"));
                       return ISCSI_STATUS_AUTHENTICATION_FAILED;
               }
               break;

       default:
               break;
       }

       complete_pars(state, tx_pdu);

       return next;
}


/*
* set_first_opnegs:
*    Set the operational negotiation parameters we want to negotiate in
*    the first login request in op_neg phase.
*
*    Parameter:
*          conn     The connection
*          state    Negotiation state
*/

STATIC void
set_first_opnegs(connection_t *conn, negotiation_state_t *state)
{
       iscsi_login_parameters_t *lpar = conn->c_login_par;
       negotiation_parameter_t *cpar;

       /* Digests - suggest None,CRC32C unless the user forces a value */
       cpar = set_key_n(state, K_HeaderDigest,
           (lpar->is_present.HeaderDigest) ? lpar->HeaderDigest : 0);
       if (cpar != NULL && !lpar->is_present.HeaderDigest) {
               cpar->list_num = 2;
               cpar->val.nval[1] = 1;
       }

       cpar = set_key_n(state, K_DataDigest, (lpar->is_present.DataDigest)
               ? lpar->DataDigest : 0);
       if (cpar != NULL && !lpar->is_present.DataDigest) {
               cpar->list_num = 2;
               cpar->val.nval[1] = 1;
       }

       set_key_n(state, K_MaxRecvDataSegmentLength,
               conn->c_Our_MaxRecvDataSegmentLength);
       /* This is direction-specific, we may have a different default */
       state->MaxRecvDataSegmentLength =
               entries[K_MaxRecvDataSegmentLength].defval;

       /* First connection only */
       if (!conn->c_session->s_TSIH) {
               state->ErrorRecoveryLevel =
                   (lpar->is_present.ErrorRecoveryLevel) ?
                   lpar->ErrorRecoveryLevel : 2;
               /*
                * Negotiate InitialR2T to FALSE and ImmediateData to
                * TRUE, should be slightly more efficient than the
                * default InitialR2T=TRUE.
                */
               state->InitialR2T = FALSE;
               state->ImmediateData = TRUE;

               /* We don't really care about this, so don't negotiate
                * by default
                */
               state->MaxBurstLength = entries[K_MaxBurstLength].defval;
               state->FirstBurstLength = entries[K_FirstBurstLength].defval;
               state->MaxOutstandingR2T = entries[K_MaxOutstandingR2T].defval;

               set_key_n(state, K_ErrorRecoveryLevel, state->ErrorRecoveryLevel);
               set_key_n(state, K_InitialR2T, state->InitialR2T);
               set_key_n(state, K_ImmediateData, state->ImmediateData);

               if (lpar->is_present.MaxConnections) {
                       state->MaxConnections = lpar->MaxConnections;
                       set_key_n(state, K_MaxConnections, lpar->MaxConnections);
               }

               if (lpar->is_present.DefaultTime2Wait)
                       set_key_n(state, K_DefaultTime2Wait, lpar->DefaultTime2Wait);
               else
                       state->DefaultTime2Wait = entries[K_DefaultTime2Wait].defval;

               if (lpar->is_present.DefaultTime2Retain)
                       set_key_n(state, K_DefaultTime2Retain, lpar->DefaultTime2Retain);
               else
                       state->DefaultTime2Retain = entries[K_DefaultTime2Retain].defval;
       } else
               init_session_parameters(conn->c_session, state);

       DEBC(conn, 10, ("SetFirstOpnegs: recover=%d, MRDSL=%d\n",
               conn->c_recover, state->MaxRecvDataSegmentLength));
}


/*
* assemble_negotiation_parameters:
*    Assemble any negotiation parameters requested by the other side.
*
*    Parameter:
*          conn     The connection
*          ccb      The login ccb
*          rx_pdu   The received login response PDU
*          tx_pdu   The transmit PDU
*
*    Returns:    0     On success
*                > 0   (an ISCSI error code) if an error occurred.
*/

int
assemble_negotiation_parameters(connection_t *conn, ccb_t *ccb, pdu_t *rx_pdu,
                                                           pdu_t *tx_pdu)
{
       negotiation_state_t *state = (negotiation_state_t *) ccb->ccb_temp_data;
       negotiation_parameter_t rxp;
       uint8_t *rxpars;
       int rc;

       state->num_pars = 0;

       DEBC(conn, 10, ("AsmNegParams: connState=%d, MRDSL=%d\n",
               conn->c_state, state->MaxRecvDataSegmentLength));

       if (conn->c_state == ST_SEC_NEG || conn->c_state == ST_SEC_FIN) {
               conn->c_state = ST_OP_NEG;
               set_first_opnegs(conn, state);
       }

       rxpars = (uint8_t *) rx_pdu->pdu_temp_data;
       if (rxpars != NULL) {
               /* Note: There are always at least 2 extra bytes past temp_data_len */
               rxpars[rx_pdu->pdu_temp_data_len] = '\0';
               rxpars[rx_pdu->pdu_temp_data_len + 1] = '\0';

               while (*rxpars) {
                       if ((rxpars = get_parameter(rxpars, &rxp)) == NULL)
                               return ISCSI_STATUS_NEGOTIATION_ERROR;

                       rc = eval_parameter(conn, state, &rxp);
                       if (rc)
                               return rc;
               }
       }

       if (tx_pdu == NULL)
               return 0;

       complete_pars(state, tx_pdu);

       return 0;
}

/*
* init_text_parameters:
*    Initialize text negotiation.
*
*    Parameter:
*          conn     The connection
*          tx_pdu   The transmit PDU
*
*    Returns:    0     On success
*                > 0   (an ISCSI error code) if an error occurred.
*/

int
init_text_parameters(connection_t *conn, ccb_t *ccb)
{
       negotiation_state_t *state;

       state = malloc(sizeof(*state), M_TEMP, M_WAITOK | M_ZERO);
       if (state == NULL) {
               DEBOUT(("*** Out of memory in init_text_params\n"));
               return ISCSI_STATUS_NO_RESOURCES;
       }
       ccb->ccb_temp_data = state;

       state->HeaderDigest = conn->c_HeaderDigest;
       state->DataDigest = conn->c_DataDigest;
       state->MaxRecvDataSegmentLength = conn->c_MaxRecvDataSegmentLength;
       init_session_parameters(conn->c_session, state);

       return 0;
}


/*
* assemble_send_targets:
*    Assemble send targets request
*
*    Parameter:
*          pdu      The transmit PDU
*          val      The SendTargets key value
*
*    Returns:    0     On success
*                > 0   (an ISCSI error code) if an error occurred.
*/

int
assemble_send_targets(pdu_t *pdu, uint8_t *val)
{
       negotiation_parameter_t par;
       uint8_t *buf;
       int len;

       par.key = K_SendTargets;
       par.list_num = 1;
       par.val.sval = val;
       par.hex_bignums = false;

       len = parameter_size(&par);

       if ((buf = malloc(len, M_TEMP, M_WAITOK)) == NULL) {
               DEBOUT(("*** Out of memory in assemble_send_targets\n"));
               return ISCSI_STATUS_NO_RESOURCES;
       }
       pdu->pdu_temp_data = buf;
       pdu->pdu_temp_data_len = len;

       if (put_parameter(buf, len, &par) == 0) {
               DEBOUT(("trying to put zero sized buffer\n"));
               return ISCSI_STATUS_PARAMETER_INVALID;
       }

       return 0;
}


/*
* set_negotiated_parameters:
*    Copy the negotiated parameters into the connection and session structure.
*
*    Parameter:
*          ccb      The ccb containing the state information
*/

void
set_negotiated_parameters(ccb_t *ccb)
{
       negotiation_state_t *state = (negotiation_state_t *) ccb->ccb_temp_data;
       connection_t *conn = ccb->ccb_connection;
       session_t *sess = ccb->ccb_session;

       conn->c_HeaderDigest = state->HeaderDigest;
       conn->c_DataDigest = state->DataDigest;
       sess->s_ErrorRecoveryLevel = state->ErrorRecoveryLevel;
       sess->s_InitialR2T = state->InitialR2T;
       sess->s_ImmediateData = state->ImmediateData;
       conn->c_MaxRecvDataSegmentLength = state->MaxRecvDataSegmentLength;
       sess->s_MaxConnections = state->MaxConnections;
       sess->s_DefaultTime2Wait = conn->c_Time2Wait = state->DefaultTime2Wait;
       sess->s_DefaultTime2Retain = conn->c_Time2Retain =
               state->DefaultTime2Retain;

       /* set idle connection timeout to half the Time2Retain window so we */
       /* don't miss it, unless Time2Retain is ridiculously small. */
       conn->c_idle_timeout_val = (conn->c_Time2Retain >= 10) ?
               (conn->c_Time2Retain / 2) * hz : CONNECTION_IDLE_TIMEOUT;

       sess->s_MaxBurstLength = state->MaxBurstLength;
       sess->s_FirstBurstLength = state->FirstBurstLength;
       sess->s_MaxOutstandingR2T = state->MaxOutstandingR2T;

       DEBC(conn, 10,("SetNegPar: MRDSL=%d, MBL=%d, FBL=%d, IR2T=%d, ImD=%d\n",
               state->MaxRecvDataSegmentLength, state->MaxBurstLength,
               state->FirstBurstLength, state->InitialR2T,
               state->ImmediateData));

       conn->c_max_transfer = min(sess->s_MaxBurstLength, conn->c_MaxRecvDataSegmentLength);

       conn->c_max_firstimmed = (!sess->s_ImmediateData) ? 0 :
                               min(sess->s_FirstBurstLength, conn->c_max_transfer);

       conn->c_max_firstdata = (sess->s_InitialR2T || sess->s_FirstBurstLength < conn->c_max_firstimmed) ? 0 :
                               min(sess->s_FirstBurstLength - conn->c_max_firstimmed, conn->c_max_transfer);

}