idm_text.c revision bc63bc88c27b6aa62c05ff64a17306bd9cd95a66
/*
* 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 usr/src/OPENSOLARIS.LICENSE
* 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 usr/src/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.
*/
#include <sys/sysmacros.h>
#include <sys/iscsi_protocol.h>
extern int
static const char idm_hex_to_ascii[] = "0123456789abcdefABCDEF";
static const idm_kv_xlate_t idm_kvpair_xlate[] = {
/*
* iSCSI Security Text Keys and Authentication Methods
*/
/*
* 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 */
/* SPKM */
/*
* SRP
* U, s, A, B, M, and H(A | M | K) are defined in [RFC2945]
*/
/*
* CHAP
*/
/*
* ISCSI Operational Parameter Keys
*/
{ KI_TARGET_PORTAL_GROUP_TAG, "TargetPortalGroupTag",
KT_NUMERICAL, B_TRUE },
{ KI_MAX_RECV_DATA_SEGMENT_LENGTH, "MaxRecvDataSegmentLength",
{ KI_MAX_BURST_LENGTH, "MaxBurstLength",
{ KI_FIRST_BURST_LENGTH, "FirstBurstLength",
{ KI_DEFAULT_TIME_2_WAIT, "DefaultTime2Wait",
{ KI_DEFAULT_TIME_2_RETAIN, "DefaultTime2Retain",
{ KI_MAX_OUTSTANDING_R2T, "MaxOutstandingR2T",
{ KI_DATA_SEQUENCE_IN_ORDER, "DataSequenceInOrder",
KT_BOOLEAN, B_FALSE },
{ KI_ERROR_RECOVERY_LEVEL, "ErrorRecoveryLevel",
/*
* iSER-specific keys
*/
{ KI_TARGET_RECV_DATA_SEGMENT_LENGTH, "TargetRecvDataSegmentLength",
"InitiatorRecvDataSegmentLength",
{ KI_MAX_OUTSTANDING_UNEXPECTED_PDUS, "MaxOutstandingUnexpectedPDUs",
/*
* Table terminator. The type KT_TEXT will allow the response
* value of "NotUnderstood".
*/
};
#define TEXTBUF_CHUNKSIZE 8192
typedef struct {
char *itb_mem;
int itb_offset;
int itb_mem_len;
/*
* Ignore all but the following keys during security negotiation
*
* SessionType
* InitiatorName
* TargetName
* TargetAddress
* InitiatorAlias
* TargetAlias
* TargetPortalGroupTag
* AuthMethod and associated auth keys
*/
char *value);
/*
* 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
{
int rc = 0;
tblen = *textbuflen;
for (;;) {
break;
}
/* Something was wrong with either the key or value */
break;
}
if (tblen == 0) {
/* End of text buffer */
break;
}
}
*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
{
}
static int
{
/*
* Caller doesn't need "valuelen" returned since "value" will
* always be a NULL-terminated string.
*/
/*
* How many bytes to the first '\0'? This represents the total
*/
/*
* 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)
*/
/* No '=', bad format */
return (EINVAL);
}
/*
* The remaining text after the '=' is the value
*/
return (0);
}
const idm_kv_xlate_t *
{
/*
* 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.
*/
/*
* 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.
*/
/* Exit the loop since we found a match */
break;
}
/* No match, look at the next entry */
ikvx++;
}
return (ikvx);
}
static int
{
int rc;
switch (ikvx->ik_idm_type) {
case KT_TEXT:
case KT_SIMPLE:
case KT_ISCSI_NAME:
case KT_ISCSI_LOCAL_NAME:
break;
case KT_BOOLEAN:
break;
case KT_REGULAR_BINARY:
case KT_LARGE_BINARY:
case KT_BINARY:
break;
case KT_LARGE_NUMERICAL:
value);
break;
case KT_NUMERICAL:
value);
break;
case KT_NUMERIC_RANGE:
value);
break;
case KT_LIST_OF_VALUES:
value);
break;
default:
ASSERT(0); /* This should never happen */
break;
}
if (rc != 0) {
/* could be one of the text constants */
}
return (rc);
}
static int
{
}
static int
{
int rc;
} else {
return (EINVAL);
}
return (rc);
}
static boolean_t
{
}
static boolean_t
kv_is_base64(char *value)
{
}
static int
{
int rc;
int value_length;
int binary_length;
/*
* 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). 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.
*/
binary_array, binary_length) != 0) {
return (EINVAL);
}
return (rc);
} else if (kv_is_base64(value)) {
if (binary_array == NULL) {
return (ENOMEM);
}
return (EINVAL);
}
return (rc);
} else {
/*
* Decimal value (not permitted for "large-binary_value" so
* it must be smaller than 64 bits. It's not really
* clear from the RFC what a decimal-binary-value might
* represent but presumably it should be treated the same
* as a hex or base64 value. Therefore we'll convert it
* to an array of bytes.
*/
(u_longlong_t *)&uint64_value)) != 0)
return (rc);
return (rc);
}
/* NOTREACHED */
}
static int
{
/*
* 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.
*/
}
static int
{
int rc;
/*
* "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.
*/
if (rc == 0) {
}
return (rc);
}
static int
{
int rc;
/* We'll store the range an an nvlist with two values */
if (rc != 0) {
return (rc);
}
/*
* We expect idm_keyvalue_get_next to ensure the string is
* terminated
*/
/*
* Find range separator
*/
/* invalid range */
return (EINVAL);
}
/*
* Start value
*/
if (rc == 0) {
}
if (rc != 0) {
return (rc);
}
/*
* End value
*/
if (rc == 0) {
}
if (rc != 0) {
return (rc);
}
/*
* Now add the "range" nvlist to the main nvlist
*/
if (rc != 0) {
return (rc);
}
return (0);
}
static int
{
char value_name[8];
char *val_scan = value_list;
int value_index = 0;
int val_len, val_list_len;
int rc;
if (rc != 0) {
return (rc);
}
/*
* We expect idm_keyvalue_get_next to ensure the string is
* terminated
*/
if (val_list_len == 0) {
return (EINVAL);
}
for (;;) {
}
if (rc != 0) {
return (rc);
}
/*
* Move to next value, see if we're at the end of the value
* list
*/
break;
}
value_index++;
}
if (rc != 0) {
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
int *validlen)
{
int rc = 0;
for (;;) {
/* Last nvpair in nvlist, we're done */
break;
}
break;
}
}
return (rc);
}
static int
{
int rc = 0;
char *key;
const idm_kv_xlate_t *ikvx;
/*
* Any key supplied by the initiator that is not in our table
* will be responded to with the string value "NotUnderstood".
* An example is a vendor specific key.
*/
/*
* The matching entry in the table will tell us how to encode
* the key and value in the nvlist.
*/
switch (ikvx->ik_idm_type) {
case KT_TEXT:
case KT_SIMPLE:
case KT_ISCSI_NAME:
case KT_ISCSI_LOCAL_NAME:
break;
case KT_BOOLEAN:
break;
case KT_REGULAR_BINARY:
case KT_LARGE_BINARY:
case KT_BINARY:
break;
case KT_LARGE_NUMERICAL:
break;
case KT_NUMERICAL:
break;
case KT_NUMERIC_RANGE:
break;
case KT_LIST_OF_VALUES:
break;
default:
ASSERT(0); /* This should never happen */
break;
}
return (rc);
}
/* ARGSUSED */
static int
{
char *key_name;
char *value;
int rc;
/* Start with the key name */
/* Add separator */
/* Add value */
/* Add trailing 0x00 */
return (0);
}
/* ARGSUSED */
static int
{
char *key_name;
int rc;
/* Start with the key name */
/* Add separator */
/* Add value */
/* Add trailing 0x00 */
return (0);
}
/* ARGSUSED */
static int
{
char *key_name;
unsigned char *value;
unsigned int len;
unsigned long n;
int rc;
/* Start with the key name */
/* Add separator */
/* Add value */
while (len > 0) {
n = *value++;
len--;
}
/* Add trailing 0x00 */
return (0);
}
/* ARGSUSED */
static int
{
ASSERT(0);
return (0);
}
/* ARGSUSED */
static int
{
char *key_name;
int rc;
char str[16];
/* Start with the key name */
/* Add separator */
/* Add value */
/* Add trailing 0x00 */
return (0);
}
/* ARGSUSED */
static int
{
ASSERT(0);
return (0);
}
/* ARGSUSED */
static int
{
char *key_name;
char *vchoice_string = NULL;
int rc;
/* Start with the key name */
/* Add separator */
/* Add value choices */
/* Add ',' between choices */
}
}
/* Add trailing 0x00 */
return (0);
}
static void
{
char *new_mem;
int new_mem_len;
}
}
static void
{
}
static void
{
}
static void
{
textbuf_makeroom(itb, sizeof (char));
itb->itb_offset++;
}
static void
{
}
static int
{
char enc_char = *enc_hex_byte;
} else {
return (EINVAL);
}
enc_hex_byte++;
enc_char = *enc_hex_byte;
} else {
return (EINVAL);
}
return (0);
}
{
char tmpstr[2];
/*
* 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';
return (EINVAL);
}
hstr++;
binary_scan++;
}
return (EINVAL);
}
hstr += 2;
binary_scan++;
}
return (0);
}
static size_t
{
const char *ptr;
return (maxlen);
}
{
const char *p, *q;
for (q = string; *q != '\0'; ++q) {
for (p = charset; *p != '\0' && *p != *q; )
p++;
if (*p != '\0') {
break;
}
}
}
/*
* 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 *
{
int nvrc;
switch (nvp_type) {
case DATA_TYPE_NVLIST:
break;
case DATA_TYPE_STRING:
/* Single choice */
result = value_list;
} else {
}
break;
default:
ASSERT(0); /* Malformed choice list */
break;
}
return (result);
}
idm_nvstat_to_kvstat(int nvrc)
{
switch (nvrc) {
case 0:
result = KV_HANDLED;
break;
case ENOMEM:
break;
case EINVAL:
break;
case EFAULT:
case ENOTSUP:
default:
break;
}
return (result);
}
void
{
switch (kvrc) {
case KV_HANDLED:
case KV_HANDLED_NO_TRANSIT:
break;
case KV_UNHANDLED:
case KV_TARGET_ONLY:
/* protocol error */
break;
case KV_VALUE_ERROR:
/* invalid value */
break;
case KV_NO_RESOURCES:
/* no memory */
break;
case KV_MISSING_FIELDS:
break;
case KV_AUTH_FAILED:
/* authentication failed */
break;
default:
/* target error */
break;
}
}
int
{
const idm_kv_xlate_t *ikvx;
char *nkey;
int rc;
/*
* key is not a NULL terminated string, so create one
*/
return (rc);
}
}
int
{
int i;
for (i = 0; i < KI_MAX_KEY; i++) {
return
&idm_kvpair_xlate[i], value));
}
}
return (EFAULT);
}
char *
{
int i;
for (i = 0; i < KI_MAX_KEY; i++) {
return (idm_kvpair_xlate[i].ik_key_name);
}
}
return (NULL);
}
/*
* return the value in a buffer that must be freed by the caller
*/
char *
{
char *str;
if (rv != 0)
return (NULL);
return (NULL);
}
/* free the allocation done in idm_textbuf_add_nvpair */
return (str);
}
/*
* build an iscsi text buffer - the memory gets freed in
* idm_itextbuf_free
*/
void *
{
char *textbuf;
int validlen, textbuflen;
&validlen) != IDM_STATUS_SUCCESS) {
return (NULL);
}
return ((void *)itb);
}
/*
* Update the pdu data up to min of max_xfer_len or data left.
* The first call to this routine should send
* a NULL bufptr. Subsequent calls send in the buffer returned.
* Call this routine until the string returned is NULL
*/
char *
{
int send = 0;
/* first call - check the length */
itb->itb_offset);
*transit = 1;
return (NULL);
}
/* we have more data than will fit in one pdu */
} else {
*transit = 1;
return (NULL);
}
/* we have more data then will fit in one pdu */
}
/* break after key, after =, after the value or after '\0' */
/* if next char is an '=' or '\0' send it */
send = 1;
}
}
if (!send) {
ptr--;
}
}
*transit = 0;
return (++ptr);
}
void
idm_itextbuf_free(void *arg)
{
}
/*
* Allocate an nvlist and poputlate with key=value from the pdu list.
* NOTE: caller must free the list
*/
{
int textbuflen, leftover_textbuflen = 0;
char *split_kvbuf;
int split_kvbuflen, cont_fraglen;
int rc;
int ret = IDM_STATUS_SUCCESS;
if (rc != 0) {
goto cleanup;
}
/*
* A login request can be split across multiple PDU's. The state
* machine has collected all the PDU's that make up this login request
* and assembled them on the "icl_pdu_list" queue. Process each PDU
* and convert the text keywords to nvlist form.
*/
if (textbuflen == 0) {
/* This shouldn't really happen but it could.. */
continue;
}
/*
* If we encountered a split key-value pair on the last
* PDU then handle it now by grabbing the remainder of the
* key-value pair from the next PDU and splicing them
* together. Obviously on the first PDU this will never
* happen.
*/
if (split_kv) {
/*
* This key-value pair spans more than two
* PDU's. We don't handle this.
*/
goto cleanup;
}
if (split_kvbuf == NULL) {
goto cleanup;
}
if (idm_textbuf_to_nvlist(*nvlist,
&split_kvbuf, &split_kvbuflen) != 0) {
/*
* Need to handle E2BIG case, indicating that
* a key-value pair is split across multiple
* PDU's.
*/
goto cleanup;
}
/* Now handle the remainder of the PDU as normal */
}
/*
* Convert each key-value pair in the text buffer to nvlist
* format. If the list has already been created the nvpair
* elements will be added on to the existing list. Otherwise
* a new nvlist will be created.
*/
if (idm_textbuf_to_nvlist(*nvlist,
&textbuf, &textbuflen) != 0) {
goto cleanup;
}
(textbuflen != 0)) {
/*
* Key-value pair is split over two PDU's. We
* assume it willl never be split over more than
* two PDU's.
*/
} else {
if (textbuflen != 0) {
/*
* Incomplete keyword but no additional
* PDU's. This is a malformed login
* request.
*/
*error_detail =
goto cleanup;
}
}
}
/*
* Free any remaining PDUs on the list. This will only
* happen if there were errors encountered during
* processing of the textbuf.
*/
}
/*
* If there were no errors, we have a complete nvlist representing
* all the iSCSI key-value pairs in the login request PDU's
* that make up this request.
*/
return (ret);
}
/*
* idm_strtoull
*
* Since the kernel only provides ddi_strtoul (not the ddi_strtoull that we
* would like to use) we need our own conversion function to convert
* string integer representations to 64-bit integers. ddi_strtoul doesn't
* work well for us on 32-bit systems. This code is shamelessly ripped
* from ddi_strtol.c. Eventually we should push this back into the DDI
* so that we don't need our own version anymore.
*/
#define DIGIT(x) \
#define lisalnum(x) \
#ifndef ULLONG_MAX
#define ULLONG_MAX 18446744073709551615ULL
#endif
int
unsigned long long *result)
{
unsigned long long val;
int c;
int xx;
unsigned long long multmax;
int neg = 0;
if (ptr != (const char **)0)
/* base is invalid -- should be a fatal error */
return (EINVAL);
}
while (isspace(c))
c = *++ustr;
switch (c) {
case '-':
neg++;
/* FALLTHROUGH */
case '+':
c = *++ustr;
}
}
if (base == 0)
if (c != '0')
base = 10;
base = 16;
else
base = 8;
/*
* for any base > 10, the digits incrementally following
* 9 are assumed to be "abc...z" or "ABC...Z"
*/
return (EINVAL); /* no number formed */
goto overflow;
goto overflow;
c = *++ustr;
}
if (ptr != (const char **)0)
return (0);
c = *++ustr;
if (ptr != (const char **)0)
return (ERANGE);
}