--- /dev/null Fri Apr 4 13:31:05 2008 +++ new/src/sun_nws/comstar/port_providers/iscsit/src/iscsit_util.c Fri Apr 4 13:31:05 2008 @@ -0,0 +1,1273 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at src/sun_nws/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at src/sun_nws/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "@(#)iscsit_util.c 1.10 08/02/29 SMI" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +static const char iscsi_hex_to_ascii[] = "0123456789abcdefABCDEF"; +static const char iscsi_base64_to_ascii[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +static const iscsi_kv_xlate_t iscsi_kvpair_xlate[] = { + /* + * iSCSI Security Text Keys and Authentication Methods + */ + + { KI_AUTH_METHOD, "AuthMethod", KT_LIST_OF_VALUES, B_FALSE }, + /* + * For values with RFC comments we need to read the RFC to see + * what type is appropriate. For now just treat the value as + * text. + */ + + /* Kerberos */ + { KI_KRB_AP_REQ, "KRB_AP_REQ", KT_TEXT /* RFC1510 */, B_TRUE}, + { KI_KRB_AP_REP, "KRB_AP_REP", KT_TEXT /* RFC1510 */, B_TRUE}, + + /* SPKM */ + { KI_SPKM_REQ, "SPKM_REQ", KT_TEXT /* RFC2025 */, B_TRUE}, + { KI_SPKM_ERROR, "SPKM_ERROR", KT_TEXT /* RFC2025 */, B_TRUE}, + { KI_SPKM_REP_TI, "SPKM_REP_TI", KT_TEXT /* RFC2025 */, B_TRUE}, + { KI_SPKM_REP_IT, "SPKM_REP_IT", KT_TEXT /* RFC2025 */, B_TRUE}, + + /* + * SRP + * U, s, A, B, M, and H(A | M | K) are defined in [RFC2945] + */ + { KI_SRP_U, "SRP_U", KT_TEXT /* */, B_TRUE}, + { KI_TARGET_AUTH, "TargetAuth", KT_BOOLEAN, B_TRUE}, + { KI_SRP_GROUP, "SRP_GROUP", KT_LIST_OF_VALUES /* */, B_FALSE}, + { KI_SRP_A, "SRP_A", KT_TEXT /* */, B_TRUE}, + { KI_SRP_B, "SRP_B", KT_TEXT /* */, B_TRUE}, + { KI_SRP_M, "SRP_M", KT_TEXT /* */, B_TRUE}, + { KI_SRM_HM, "SRP_HM", KT_TEXT /* */, B_TRUE}, + + /* + * CHAP + */ + { KI_CHAP_A, "CHAP_A", KT_LIST_OF_VALUES /* */, B_FALSE }, + { KI_CHAP_I, "CHAP_I", KT_NUMERICAL /* */, B_TRUE }, + { KI_CHAP_C, "CHAP_C", KT_BINARY /* */, B_TRUE }, + { KI_CHAP_N, "CHAP_N", KT_TEXT /* */, B_TRUE }, + { KI_CHAP_R, "CHAP_R", KT_BINARY /* */, B_TRUE }, + + + /* + * ISCSI Operational Parameter Keys + */ + { KI_HEADER_DIGEST, "HeaderDigest", KT_LIST_OF_VALUES, B_FALSE }, + { KI_DATA_DIGEST, "DataDigest", KT_LIST_OF_VALUES, B_FALSE }, + { KI_MAX_CONNECTIONS, "MaxConnections", KT_NUMERICAL, B_FALSE }, + { KI_SEND_TARGETS, "SendTargets", KT_TEXT, B_FALSE }, + { KI_TARGET_NAME, "TargetName", KT_ISCSI_NAME, B_TRUE}, + { KI_INITIATOR_NAME, "InitiatorName", KT_ISCSI_NAME, B_TRUE}, + { KI_TARGET_ALIAS, "TargetAlias", KT_ISCSI_LOCAL_NAME, B_TRUE}, + { KI_INITIATOR_ALIAS, "InitiatorAlias", KT_ISCSI_LOCAL_NAME, B_TRUE}, + /* + * XXX... it would probably be handy to parse TargetAddress into + * an nvlist containing the component parts + */ + { KI_TARGET_ADDRESS, "TargetAddress", KT_TEXT, B_TRUE}, + { KI_TARGET_PORTAL_GROUP_TAG, "TargetPortalGroupTag", + KT_BINARY, B_TRUE }, + { KI_INITIAL_R2T, "InitialR2T", KT_BOOLEAN, B_FALSE }, + { KI_IMMEDIATE_DATA, "ImmediateData", KT_BOOLEAN, B_FALSE }, + { KI_MAX_RECV_DATA_SEGMENT_LENGTH, "MaxRecvDataSegmentLength", + KT_NUMERICAL /* 512 to 2^24 - 1 */, B_TRUE }, + { KI_MAX_BURST_LENGTH, "MaxBurstLength", + KT_NUMERICAL /* 512 to 2^24 - 1 */, B_FALSE }, + { KI_FIRST_BURST_LENGTH, "FirstBurstLength", + KT_NUMERICAL /* 512 to 2^24 - 1 */, B_FALSE }, + { KI_DEFAULT_TIME_2_WAIT, "DefaultTime2Wait", + KT_NUMERICAL /* 0 to 2600 */, B_FALSE }, + { KI_DEFAULT_TIME_2_RETAIN, "DefaultTime2Retain", + KT_NUMERICAL /* 0 to 2600 */, B_FALSE }, + { KI_MAX_OUTSTANDING_R2T, "MaxOutstandingR2T", + KT_NUMERICAL /* 1 to 65535 */, B_FALSE }, + { KI_DATA_PDU_IN_ORDER, "DataPDUInOrder", KT_BOOLEAN, B_FALSE }, + { KI_DATA_SEQUENCE_IN_ORDER, "DataSequenceInOrder", + KT_BOOLEAN, B_FALSE }, + { KI_ERROR_RECOVERY_LEVEL, "ErrorRecoveryLevel", + KT_NUMERICAL /* 0 to 2 */, B_FALSE }, + { KI_SESSION_TYPE, "SessionType", KT_TEXT, B_TRUE }, + { KI_OFMARKER, "OFMarker", KT_BOOLEAN, B_FALSE }, + { KI_OFMARKERINT, "OFMarkerInt", KT_NUMERIC_RANGE, B_FALSE }, + { KI_IFMARKER, "IFMarker", KT_BOOLEAN, B_FALSE }, + { KI_IFMARKERINT, "IFMarkerInt", KT_NUMERIC_RANGE, B_FALSE }, + + { KI_MAX_KEY, NULL, KT_TEXT, B_TRUE } /* Terminator */ +}; + + +#define TEXTBUF_CHUNKSIZE 8192 + +typedef struct { + char *itb_mem; + int itb_offset; + int itb_mem_len; +} iscsi_textbuf_t; + +/* + * XXX Ignore all but the following keys during security negotiation + * + * SessionType + * InitiatorName + * TargetName + * TargetAddress + * InitiatorAlias + * TargetAlias + * TargetPortalGroupTag + * AuthMethod and associated auth keys + */ + +static int iscsi_keyvalue_get_next(char **tb_scan, int *tb_len, + char **key, int *keylen, char **value); + +static int iscsi_nvlist_add_keyvalue(nvlist_t *nvl, + char *key, int keylen, char *value); + +static int iscsi_nvlist_add_string(nvlist_t *nvl, + const iscsi_kv_xlate_t *ikvx, char *value); + +static int iscsi_nvlist_add_boolean(nvlist_t *nvl, + const iscsi_kv_xlate_t *ikvx, char *value); + +static int iscsi_nvlist_add_binary(nvlist_t *nvl, + const iscsi_kv_xlate_t *ikvx, char *value); + +static int iscsi_nvlist_add_large_numerical(nvlist_t *nvl, + const iscsi_kv_xlate_t *ikvx, char *value); + +static int iscsi_nvlist_add_numerical(nvlist_t *nvl, + const iscsi_kv_xlate_t *ikvx, char *value); + +static int iscsi_nvlist_add_numeric_range(nvlist_t *nvl, + const iscsi_kv_xlate_t *ikvx, char *value); + +static int iscsi_nvlist_add_list_of_values(nvlist_t *nvl, + const iscsi_kv_xlate_t *ikvx, char *value); + +static int iscsi_itextbuf_add_nvpair(nvpair_t *nvp, iscsi_textbuf_t *itb); + +static int iscsi_itextbuf_add_string(nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx, iscsi_textbuf_t *itb); + +static int iscsi_itextbuf_add_boolean(nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx, iscsi_textbuf_t *itb); + +static int iscsi_itextbuf_add_binary(nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx, iscsi_textbuf_t *itb); + +static int iscsi_itextbuf_add_large_numerical(nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx, iscsi_textbuf_t *itb); + +static int iscsi_itextbuf_add_numerical(nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx, iscsi_textbuf_t *itb); + +static int iscsi_itextbuf_add_numeric_range(nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx, iscsi_textbuf_t *itb); + +static int iscsi_itextbuf_add_list_of_values(nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx, iscsi_textbuf_t *itb); + +static void textbuf_memcpy(iscsi_textbuf_t *itb, void *mem, int mem_len); + +static void textbuf_strcpy(iscsi_textbuf_t *itb, char *str); + +static void textbuf_append_char(iscsi_textbuf_t *itb, char c); + +static void textbuf_terminate_kvpair(iscsi_textbuf_t *itb); + +static int iscsit_ascii_to_hex(char *enc_hex_byte, uint8_t *bin_val); + +static int iscsit_base16_str_to_binary(char *hstr, int hstr_len, + uint8_t *binary, int binary_length); + +static int iscsit_base64_str_to_binary(char *hstr, int hstr_len, + uint8_t *binary, int binary_length); + +static size_t iscsit_strnlen(const char *str, size_t maxlen); + +static size_t iscsit_strcspn(const char *string, const char *charset); + + +/* + * Processes all whole iSCSI name-value pairs in a text buffer and adds + * a corresponding Solaris nvpair_t to the provided nvlist. If the last + * iSCSI name-value pair in textbuf is truncated (which can occur when + * the request spans multiple PDU's) then upon return textbuf will + * point to the truncated iSCSI name-value pair in the buffer and + * textbuflen will contain the remaining bytes in the buffer. The + * caller can save off this fragment of the iSCSI name-value pair for + * use when the next PDU in the request arrives. + * + * textbuflen includes the trailing 0x00! + */ + +int +iscsi_textbuf_to_nvlist(nvlist_t *nvl, char **textbuf, int *textbuflen) +{ + int rc = 0; + char *tbscan, *key, *value; + int tblen, keylen; + + tbscan = *textbuf; + tblen = *textbuflen; + + for (;;) { + if ((rc = iscsi_keyvalue_get_next(&tbscan, &tblen, + &key, &keylen, &value)) != 0) { + /* There was a problem reading the key/value pair */ + break; + } + + if ((rc = iscsi_nvlist_add_keyvalue(nvl, + key, keylen, value)) != 0) { + /* Something was wrong with either the key or value */ + break; + } + + if (tblen == 0) { + /* End of text buffer */ + break; + } + } + + *textbuf = tbscan; + *textbuflen = tblen; + + return (rc); +} + +/* + * If a test buffer starts with an ISCSI name-value pair fragment (a + * continuation from a previous buffer) return the length of the fragment + * contained in this buffer. We do not handle name-value pairs that span + * more than two buffers so if this buffer does not contain the remainder + * of the name value pair the function will return 0. If the first + * name-value pair in the buffer is complete the functionw will return 0. + */ +int +iscsi_textbuf_to_firstfraglen(void *textbuf, int textbuflen) +{ + return (iscsit_strnlen(textbuf, textbuflen)); +} + +static int +iscsi_keyvalue_get_next(char **tb_scan, int *tb_len, + char **key, int *keylen, char **value) +{ + /* + * Caller doesn't need "valuelen" returned since "value" will + * always be a NULL-terminated string. + */ + size_t total_len, valuelen; + + /* + * How many bytes to the first '\0'? This represents the total + * length of our iSCSI key/value pair. + */ + total_len = iscsit_strnlen(*tb_scan, *tb_len); + if (total_len == *tb_len) { + /* + * No '\0', perhaps this key/value pair is continued in + * another buffer + */ + return (E2BIG); + } + + /* + * Found NULL, so this is a possible key-value pair. At + * the same time we've validated that there is actually a + * NULL in this string so it's safe to use regular + * string functions (i.e. strcpy instead of strncpy) + */ + *key = *tb_scan; + *keylen = iscsit_strcspn(*tb_scan, "="); + + if (*keylen == total_len) { + /* No '=', bad format */ + return (EINVAL); + } + + *tb_scan += *keylen + 1; /* Skip the '=' */ + *tb_len -= *keylen + 1; + + /* + * The remaining text after the '=' is the value + */ + *value = *tb_scan; + valuelen = total_len - (*keylen + 1); + + *tb_scan += valuelen + 1; /* Skip the '\0' */ + *tb_len -= valuelen + 1; + + return (0); +} + +const iscsi_kv_xlate_t * +iscsi_lookup_kv_xlate(const char *key, int keylen) +{ + const iscsi_kv_xlate_t *ikvx = &iscsi_kvpair_xlate[0]; + + /* + * Look for a matching key value in the key/value pair table. + * The matching entry in the table will tell us how to encode + * the key and value in the nvlist. If we don't recognize + * the key then we will simply encode it in string format. + * The login or text request code can generate the appropriate + * "not understood" resposne. + */ + while (ikvx->ik_key_id != KI_MAX_KEY) { + /* + * Compare strings. "key" is not NULL-terminated so + * use strncmp. Since we are using strncmp we + * need to check that the lengths match, otherwise + * we might unintentionally lookup "TargetAddress" + * with a key of "Target" (or something similar). + * + * "value" is NULL-terminated so we can use it as + * a regular string. + */ + if ((strncmp(ikvx->ik_key_name, key, keylen) == 0) && + (strlen(ikvx->ik_key_name) == keylen)) { + /* Exit the loop since we found a match */ + break; + } + + /* No match, look at the next entry */ + ikvx++; + } + + return (ikvx); +} + +static int +iscsi_nvlist_add_keyvalue(nvlist_t *nvl, + char *key, int keylen, char *value) +{ + int rc = 0; + const iscsi_kv_xlate_t *ikvx; + + ikvx = iscsi_lookup_kv_xlate(key, keylen); + + if (ikvx->ik_key_id == KI_MAX_KEY) { /* XXX Make sure we test this */ + rc = nvlist_add_string(nvl, key, value); + return (rc); + } + + switch (ikvx->ik_iscsi_type) { + case KT_TEXT: + case KT_SIMPLE: + case KT_ISCSI_NAME: + case KT_ISCSI_LOCAL_NAME: + /* XXX "ISCSI local name" is UTF-8.. OK? */ + rc = iscsi_nvlist_add_string(nvl, ikvx, value); + break; + case KT_BOOLEAN: + rc = iscsi_nvlist_add_boolean(nvl, ikvx, value); + break; + case KT_REGULAR_BINARY: + case KT_LARGE_BINARY: + case KT_BINARY: + rc = iscsi_nvlist_add_binary(nvl, ikvx, value); + break; + case KT_LARGE_NUMERICAL: + rc = iscsi_nvlist_add_large_numerical(nvl, ikvx, + value); + break; + case KT_NUMERICAL: + rc = iscsi_nvlist_add_numerical(nvl, ikvx, + value); + break; + case KT_NUMERIC_RANGE: + rc = iscsi_nvlist_add_numeric_range(nvl, ikvx, + value); + break; + case KT_LIST_OF_VALUES: + rc = iscsi_nvlist_add_list_of_values(nvl, ikvx, + value); + break; + default: + ASSERT(0); /* This should never happen */ + break; + } + + return (rc); +} + + +static int +iscsi_nvlist_add_string(nvlist_t *nvl, + const iscsi_kv_xlate_t *ikvx, char *value) +{ + return (nvlist_add_string(nvl, ikvx->ik_key_name, value)); +} + + +static int +iscsi_nvlist_add_boolean(nvlist_t *nvl, + const iscsi_kv_xlate_t *ikvx, char *value) +{ + int rc; + boolean_t bool_val; + + if (strcasecmp(value, "Yes") == 0) { + bool_val = B_TRUE; + } else if (strcasecmp(value, "No") == 0) { + bool_val = B_FALSE; + } else { + return (EINVAL); + } + + rc = nvlist_add_boolean_value(nvl, ikvx->ik_key_name, bool_val); + + return (rc); +} + +static boolean_t +kv_is_hex(char *value) +{ + return ((strncmp(value, "0x", strlen("0x")) == 0) || + (strncmp(value, "0X", strlen("0X")) == 0)); +} + +static boolean_t +kv_is_base64(char *value) +{ + return ((strncmp(value, "0b", strlen("0b")) == 0) || + (strncmp(value, "0B", strlen("0B")) == 0)); +} + + +static int +iscsi_nvlist_add_binary(nvlist_t *nvl, + const iscsi_kv_xlate_t *ikvx, char *value) +{ + int rc; + int value_length; + int binary_length; + char tmpstr[2]; + long tmpval; + uchar_t *binary_array; + + /* + * A binary value can be either decimal, hex or base64. If it's + * decimal then the encoded string must be less than 64 bits in + * length (8 characters) XXX. In all cases we will convert the + * included value to a byte array starting with the MSB. The + * assumption is that values meant to be treated as integers will + * use the "numerical" and "large numerical" types. + */ + if (kv_is_hex(value)) { + value += strlen("0x"); + value_length = strlen(value); + binary_length = (value_length + 1) / 2; + binary_array = kmem_alloc(binary_length, KM_NOSLEEP); + if (binary_array == NULL) { + return (ENOMEM); + } + + if (iscsit_base16_str_to_binary(value, value_length, + binary_array, binary_length) != 0) { + kmem_free(binary_array, binary_length); + return (EINVAL); + } + + rc = nvlist_add_byte_array(nvl, ikvx->ik_key_name, + binary_array, binary_length); + + kmem_free(binary_array, binary_length); + + return (rc); + + } else if (kv_is_base64(value)) { + value += strlen("0b"); + value_length = strlen(value); + binary_length = (value_length * 2); + binary_array = kmem_alloc(binary_length, KM_NOSLEEP); + if (binary_array == NULL) { + return (ENOMEM); + } + + if (iscsit_base64_str_to_binary(value, value_length, + binary_array, binary_length) != 0) { + kmem_free(binary_array, binary_length); + return (EINVAL); + } + + rc = nvlist_add_byte_array(nvl, ikvx->ik_key_name, + binary_array, binary_length); + + kmem_free(binary_array, binary_length); + + return (rc); + } else { + /* Decimal value */ + ASSERT(0); + } + + return (0); +} + + +static int +iscsi_nvlist_add_large_numerical(nvlist_t *nvl, + const iscsi_kv_xlate_t *ikvx, char *value) +{ + /* + * A "large numerical" value can be larger than 64-bits. Since + * there is no upper bound on the size of the value, we will + * punt and store it in string form. We could also potentially + * treat the value as binary data. + */ + return (nvlist_add_string(nvl, ikvx->ik_key_name, value)); +} + + +static int +iscsi_nvlist_add_numerical(nvlist_t *nvl, + const iscsi_kv_xlate_t *ikvx, char *value) +{ + int rc; + uint64_t uint64_value; +#ifdef LATER /* XXX */ + if (iscsi_validate_numerical(value) != 0) { + return (EINVAL); + } +#endif + + /* + * "Numerical" values in the iSCSI standard are up to 64-bits wide. + * On a 32-bit system we could see an overflow here during conversion. + * This shouldn't happen with real-world values for the current + * iSCSI parameters of "numerical" type. + */ + rc = ddi_strtoul(value, NULL, 0, (unsigned long *)&uint64_value); + if (rc == 0) { + rc = nvlist_add_uint64(nvl, ikvx->ik_key_name, uint64_value); + } + + return (rc); +} + + +static int +iscsi_nvlist_add_numeric_range(nvlist_t *nvl, + const iscsi_kv_xlate_t *ikvx, char *range) +{ + char value_name[8]; + nvlist_t *range_nvl; + char *val_scan = range; + uint64_t start_val, end_val; + int val_len, range_len; + int rc; + +#ifdef LATER /* XXX */ + if (iscsi_validate_numerical_range(range) != 0) { + return (EINVAL); + } +#endif + /* We'll store the range an an nvlist with two values */ + rc = nvlist_alloc(&range_nvl, NV_UNIQUE_NAME, KM_NOSLEEP); + if (rc != 0) { + return (rc); + } + + /* + * We expect iscsi_keyvalue_get_next to ensure the string is + * terminated + */ + range_len = strlen(range); + + /* + * Find range separator + */ + val_len = iscsit_strcspn(val_scan, "~"); + + if (val_len == range_len) { + /* invalid range */ + nvlist_free(range_nvl); + return (EINVAL); + } + + /* + * Start value + */ + *(val_scan + val_len + 1) = '\0'; + rc = ddi_strtoul(val_scan, NULL, 0, (unsigned long *)&start_val); + if (rc == 0) { + rc = nvlist_add_uint64(range_nvl, "start", start_val); + } + if (rc != 0) { + nvlist_free(range_nvl); + return (rc); + } + + /* + * End value + */ + val_scan += val_len + 1; + rc = ddi_strtoul(val_scan, NULL, 0, (unsigned long *)&end_val); + if (rc == 0) { + rc = nvlist_add_uint64(range_nvl, "start", end_val); + } + if (rc != 0) { + nvlist_free(range_nvl); + return (rc); + } + + /* + * Now add the "range" nvlist to the main nvlist + */ + rc = nvlist_add_nvlist(nvl, ikvx->ik_key_name, range_nvl); + if (rc != 0) { + nvlist_free(range_nvl); + return (rc); + } + + return (0); +} + + +static int +iscsi_nvlist_add_list_of_values(nvlist_t *nvl, + const iscsi_kv_xlate_t *ikvx, char *value_list) +{ + char value_name[8]; + nvlist_t *value_list_nvl; + char *val_scan = value_list; + int value_index = 0; + int val_len, val_list_len; + int rc; + + rc = nvlist_alloc(&value_list_nvl, NV_UNIQUE_NAME, KM_NOSLEEP); + if (rc != 0) { + return (rc); + } + + /* + * We expect iscsi_keyvalue_get_next to ensure the string is + * terminated + */ + val_list_len = strlen(value_list); + if (val_list_len == 0) { + nvlist_free(value_list_nvl); + return (EINVAL); + } + + for (;;) { + (void) snprintf(value_name, 8, "value%d", value_index); + + val_len = iscsit_strcspn(val_scan, ","); + + if (*(val_scan + val_len) != '\0') { + *(val_scan + val_len) = '\0'; + } + rc = nvlist_add_string(value_list_nvl, value_name, val_scan); + if (rc != 0) { + nvlist_free(value_list_nvl); + return (rc); + } + + /* + * Move to next value, see if we're at the end of the value + * list + */ + val_scan += val_len + 1; + if (val_scan == value_list + val_list_len + 1) { + break; + } + + value_index++; + } + + rc = nvlist_add_nvlist(nvl, ikvx->ik_key_name, value_list_nvl); + if (rc != 0) { + nvlist_free(value_list_nvl); + return (rc); + } + + return (0); +} + +/* + * Convert an nvlist containing standard iSCSI key names and values into + * a text buffer with properly formatted iSCSI key-value pairs ready to + * transmit on the wire. *textbuf should be NULL and will be set to point + * the resulting text buffer. + */ + +int +iscsi_nvlist_to_textbuf(nvlist_t *nvl, char **textbuf, int *textbuflen, + int *validlen) +{ + int rc = 0; + nvpair_t *nvp = NULL; + iscsi_textbuf_t itb; + char *textbuf_offset; + int kvpairlen; /* Length of iSCSI key-value pair from nvpair_t */ + + bzero(&itb, sizeof (itb)); + + for (;;) { + nvp = nvlist_next_nvpair(nvl, nvp); + + if (nvp == NULL) { + /* Last nvpair in nvlist, we're done */ + break; + } + + if ((rc = iscsi_itextbuf_add_nvpair(nvp, &itb)) != 0) { + /* There was a problem building the key/value pair */ + break; + } + } + + *textbuf = itb.itb_mem; + *textbuflen = itb.itb_mem_len; + *validlen = itb.itb_offset; + + return (rc); +} + +static int +iscsi_itextbuf_add_nvpair(nvpair_t *nvp, + iscsi_textbuf_t *itb) +{ + int rc = 0; + char *key; + const iscsi_kv_xlate_t *ikvx; + + key = nvpair_name(nvp); + + ikvx = iscsi_lookup_kv_xlate(key, strlen(key)); + + /* + * Since we build the response nvlist presumably all the nvpairs + * should contain value key values. + */ + ASSERT(ikvx->ik_key_id != KI_MAX_KEY); + + /* + * Look for a matching key value in the key/value pair table. + * The matching entry in the table will tell us how to encode + * the key and value in the nvlist. + */ + switch (ikvx->ik_iscsi_type) { + case KT_TEXT: + case KT_SIMPLE: + case KT_ISCSI_NAME: + case KT_ISCSI_LOCAL_NAME: + /* XXX "ISCSI local name" is UTF-8.. OK? */ + rc = iscsi_itextbuf_add_string(nvp, ikvx, itb); + break; + case KT_BOOLEAN: + rc = iscsi_itextbuf_add_boolean(nvp, ikvx, itb); + break; + case KT_REGULAR_BINARY: + case KT_LARGE_BINARY: + case KT_BINARY: + /* + * We may need to break this into multiple + * functions for outputing to a text buf. + * XXX + */ + rc = iscsi_itextbuf_add_binary(nvp, ikvx, itb); + break; + case KT_LARGE_NUMERICAL: + rc = iscsi_itextbuf_add_large_numerical(nvp, ikvx, itb); + break; + case KT_NUMERICAL: + rc = iscsi_itextbuf_add_numerical(nvp, ikvx, itb); + break; + case KT_NUMERIC_RANGE: + rc = iscsi_itextbuf_add_numeric_range(nvp, ikvx, itb); + break; + case KT_LIST_OF_VALUES: + rc = iscsi_itextbuf_add_list_of_values(nvp, ikvx, itb); + break; + default: + ASSERT(0); /* This should never happen */ + break; + } + + return (rc); +} + +static int +iscsi_itextbuf_add_string(nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx, iscsi_textbuf_t *itb) +{ + char *key_name; + char *value; + int rc; + + /* Start with the key name */ + key_name = nvpair_name(nvp); + textbuf_strcpy(itb, key_name); + + /* Add separator */ + textbuf_append_char(itb, '='); + + /* Add value */ + rc = nvpair_value_string(nvp, &value); + ASSERT(rc == 0); + textbuf_strcpy(itb, value); + + /* Add trailing 0x00 */ + textbuf_terminate_kvpair(itb); + + return (0); +} + + +static int +iscsi_itextbuf_add_boolean(nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx, iscsi_textbuf_t *itb) +{ + char *key_name; + boolean_t value; + int rc; + + /* Start with the key name */ + key_name = nvpair_name(nvp); + textbuf_strcpy(itb, key_name); + + /* Add separator */ + textbuf_append_char(itb, '='); + + /* Add value */ + rc = nvpair_value_boolean_value(nvp, &value); + ASSERT(rc == 0); + textbuf_strcpy(itb, value ? "Yes" : "No"); + + /* Add trailing 0x00 */ + textbuf_terminate_kvpair(itb); + + return (0); +} + +static int +iscsi_itextbuf_add_binary(nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx, iscsi_textbuf_t *itb) +{ + char *key_name; + unsigned char *value; + unsigned int len; + unsigned long n; + int rc; + + /* Start with the key name */ + key_name = nvpair_name(nvp); + textbuf_strcpy(itb, key_name); + + /* Add separator */ + textbuf_append_char(itb, '='); + + /* Add value */ + rc = nvpair_value_byte_array(nvp, &value, &len); + ASSERT(rc == 0); + + textbuf_strcpy(itb, "0x"); + + while (len > 0) { + n = *value++; + len--; + + textbuf_append_char(itb, iscsi_hex_to_ascii[(n >> 4) & 0xf]); + textbuf_append_char(itb, iscsi_hex_to_ascii[n & 0xf]); + } + + /* Add trailing 0x00 */ + textbuf_terminate_kvpair(itb); + + return (0); +} + +static int +iscsi_itextbuf_add_large_numerical(nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx, iscsi_textbuf_t *itb) +{ + ASSERT(0); + return (0); +} + +static int +iscsi_itextbuf_add_numerical(nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx, iscsi_textbuf_t *itb) +{ + char *key_name; + uint64_t value; + int rc; + char str[16]; + + /* Start with the key name */ + key_name = nvpair_name(nvp); + textbuf_strcpy(itb, key_name); + + /* Add separator */ + textbuf_append_char(itb, '='); + + /* Add value */ + rc = nvpair_value_uint64(nvp, &value); + ASSERT(rc == 0); + sprintf(str, "%d", value); + textbuf_strcpy(itb, str); + + /* Add trailing 0x00 */ + textbuf_terminate_kvpair(itb); + + return (0); +} + +static int +iscsi_itextbuf_add_numeric_range(nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx, iscsi_textbuf_t *itb) +{ + ASSERT(0); + return (0); +} + +static int +iscsi_itextbuf_add_list_of_values(nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx, iscsi_textbuf_t *itb) +{ + char *key_name; + nvpair_t *vchoice; + char *vchoice_string; + boolean_t value; + int rc; + + /* Start with the key name */ + key_name = nvpair_name(nvp); + textbuf_strcpy(itb, key_name); + + /* Add separator */ + textbuf_append_char(itb, '='); + + /* Add value choices */ + vchoice = iscsi_get_next_listvalue(nvp, NULL); + while (vchoice != NULL) { + rc = nvpair_value_string(vchoice, &vchoice_string); + ASSERT(rc == 0); + textbuf_strcpy(itb, vchoice_string); + vchoice = iscsi_get_next_listvalue(nvp, vchoice); + if (vchoice != NULL) { + /* Add ',' between choices */ + textbuf_append_char(itb, ','); + } + } + + /* Add trailing 0x00 */ + textbuf_terminate_kvpair(itb); + + return (0); +} + + +static void +textbuf_makeroom(iscsi_textbuf_t *itb, int size) +{ + char *new_mem; + int new_mem_len; + + if (itb->itb_mem == NULL) { + itb->itb_mem_len = MAX(TEXTBUF_CHUNKSIZE, size); + itb->itb_mem = kmem_alloc(itb->itb_mem_len, KM_SLEEP); + } else if ((itb->itb_offset + size) > itb->itb_mem_len) { + new_mem_len = itb->itb_mem_len + MAX(TEXTBUF_CHUNKSIZE, size); + new_mem = kmem_alloc(new_mem_len, KM_SLEEP); + bcopy(itb->itb_mem, new_mem, itb->itb_mem_len); + kmem_free(itb->itb_mem, itb->itb_mem_len); + itb->itb_mem = new_mem; + itb->itb_mem_len = new_mem_len; + } +} + +static void +textbuf_memcpy(iscsi_textbuf_t *itb, void *mem, int mem_len) +{ + textbuf_makeroom(itb, mem_len); + memcpy(itb->itb_mem + itb->itb_offset, mem, mem_len); + itb->itb_offset += mem_len; +} + +static void +textbuf_strcpy(iscsi_textbuf_t *itb, char *str) +{ + textbuf_memcpy(itb, str, strlen(str)); +} + +static void +textbuf_append_char(iscsi_textbuf_t *itb, char c) +{ + textbuf_makeroom(itb, sizeof (char)); + *(itb->itb_mem + itb->itb_offset) = c; + itb->itb_offset++; +} + +static void +textbuf_terminate_kvpair(iscsi_textbuf_t *itb) +{ + textbuf_append_char(itb, '\0'); +} + +static int +iscsit_ascii_to_hex(char *enc_hex_byte, uint8_t *bin_val) +{ + uint8_t nibble1, nibble2; + char enc_char = *enc_hex_byte; + + if (enc_char >= '0' && enc_char <= '9') { + nibble1 = (enc_char - '0'); + } else if (enc_char >= 'A' && enc_char <= 'F') { + nibble1 = (0xA + (enc_char - 'A')); + } else if (enc_char >= 'a' && enc_char <= 'f') { + nibble1 = (0xA + (enc_char - 'a')); + } else { + return (EINVAL); + } + + enc_hex_byte++; + enc_char = *enc_hex_byte; + + if (enc_char >= '0' && enc_char <= '9') { + nibble2 = (enc_char - '0'); + } else if (enc_char >= 'A' && enc_char <= 'F') { + nibble2 = (0xA + (enc_char - 'A')); + } else if (enc_char >= 'a' && enc_char <= 'f') { + nibble2 = (0xA + (enc_char - 'a')); + } else { + return (EINVAL); + } + + *bin_val = (nibble1 << 4) | nibble2; + + return (0); +} + + +static int iscsit_base16_str_to_binary(char *hstr, int hstr_len, + uint8_t *binary_array, int binary_length) +{ + char tmpstr[2]; + uchar_t *binary_scan; + + binary_scan = binary_array; + + /* + * If the length of the encoded ascii hex value is a multiple + * of two then every two ascii characters correspond to a hex + * byte. If the length of the value is not a multiple of two + * then the first character is the first hex byte and then for + * the remaining of the string every two ascii characters + * correspond to a hex byte + */ + if ((hstr_len % 2) != 0) { + + tmpstr[0] = '0'; + tmpstr[1] = *hstr; + + if (iscsit_ascii_to_hex(tmpstr, binary_scan) != 0) { + return (EINVAL); + } + + hstr++; + binary_scan++; + } + + while (binary_scan != binary_array + binary_length) { + if (iscsit_ascii_to_hex(hstr, binary_scan) != 0) { + return (EINVAL); + } + + hstr += 2; + binary_scan++; + } + + return (0); +} + +static int +iscsit_base64_str_to_binary(char *hstr, int hstr_len, + uint8_t *binary, int binary_length) +{ + ASSERT(0); + return (0); +} + +static size_t +iscsit_strnlen(const char *str, size_t maxlen) +{ + const char *ptr; + + ptr = memchr(str, 0, maxlen); + if (ptr == NULL) + return (maxlen); + + return (ptr - str); +} + +size_t +iscsit_strcspn(const char *string, const char *charset) +{ + const char *p, *q; + + for (q = string; *q != '\0'; ++q) { + for (p = charset; *p != '\0' && *p != *q; ++p) + ; + if (*p != '\0') + break; + } + return (q - string); +} + +/* + * We allow a list of choices to be represented as a single nvpair + * (list with one value choice), or as an nvlist with a single nvpair + * (also a list with on value choice), or as an nvlist with multiple + * nvpairs (a list with multiple value choices). This function implements + * the "get next" functionality regardless of the choice list structure. + * + * nvpair_t's that contain choices are always strings. + */ +nvpair_t * +iscsi_get_next_listvalue(nvpair_t *value_list, nvpair_t *curr_nvp) +{ + nvpair_t *result; + nvlist_t *nvl; + int nvrc; + data_type_t nvp_type; + + nvp_type = nvpair_type(value_list); + + switch (nvp_type) { + case DATA_TYPE_NVLIST: + nvrc = nvpair_value_nvlist(value_list, &nvl); + ASSERT(nvrc == 0); + result = nvlist_next_nvpair(nvl, curr_nvp); + break; + case DATA_TYPE_STRING: + /* Single choice */ + if (curr_nvp == NULL) { + result = value_list; + } else { + result = NULL; + } + break; + default: + ASSERT(0); /* Malformed choice list */ + result = NULL; + break; + } + + return (result); +} + + +int +iscsi_nvpair_cmp(nvpair_t *nvp, nvpair_t *negotiated_nvp) +{ + ASSERT(0); /* XXX */ + return (1); /* miscompare */ +} + + +kv_status_t +iscsi_nvstat_to_kvstat(int nvrc) +{ + kv_status_t result; + switch (nvrc) { + case 0: + result = KV_HANDLED; + break; + case ENOMEM: + result = KV_NO_RESOURCES; + break; + case EINVAL: + result = KV_VALUE_ERROR; + break; + case EFAULT: + case ENOTSUP: + default: + result = KV_INTERNAL_ERROR; + break; + } + + return (result); +} + +void +iscsi_kvstat_to_error(kv_status_t kvrc, uint8_t *class, uint8_t *detail) +{ + switch (kvrc) { + case KV_HANDLED: + *class = ISCSI_STATUS_CLASS_SUCCESS; + *detail = ISCSI_LOGIN_STATUS_ACCEPT; + break; + case KV_UNHANDLED: + case KV_TARGET_ONLY: + /* protocol error */ + *class = ISCSI_STATUS_CLASS_INITIATOR_ERR; + *detail = ISCSI_LOGIN_STATUS_INVALID_REQUEST; + break; + case KV_VALUE_ERROR: + /* invalid value */ + *class = ISCSI_STATUS_CLASS_INITIATOR_ERR; + *detail = ISCSI_LOGIN_STATUS_INIT_ERR; + break; + case KV_NO_RESOURCES: + /* no memory */ + *class = ISCSI_STATUS_CLASS_TARGET_ERR; + *detail = ISCSI_LOGIN_STATUS_NO_RESOURCES; + break; + case KV_MISSING_FIELDS: + /* key/value pair(s) missing */ + *class = ISCSI_STATUS_CLASS_INITIATOR_ERR; + *detail = ISCSI_LOGIN_STATUS_MISSING_FIELDS; + break; + case KV_AUTH_FAILED: + /* authentication failed */ + *class = ISCSI_STATUS_CLASS_INITIATOR_ERR; + *detail = ISCSI_LOGIN_STATUS_AUTH_FAILED; + break; + default: + /* target error */ + *class = ISCSI_STATUS_CLASS_TARGET_ERR; + *detail = ISCSI_LOGIN_STATUS_TARGET_ERROR; + break; + } +}