/* * 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 * 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 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 #include #include #include #include #include #include #include #include #include extern int iscsi_base64_str_to_binary(char *hstr, int hstr_len, uint8_t *binary, int binary_buf_len, int *out_len); static const char idm_hex_to_ascii[] = "0123456789abcdefABCDEF"; static const idm_kv_xlate_t idm_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}, { KI_TARGET_ADDRESS, "TargetAddress", KT_TEXT, B_TRUE}, { KI_TARGET_PORTAL_GROUP_TAG, "TargetPortalGroupTag", KT_NUMERICAL, 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 }, /* * iSER-specific keys */ { KI_RDMA_EXTENSIONS, "RDMAExtensions", KT_BOOLEAN, B_FALSE }, { KI_TARGET_RECV_DATA_SEGMENT_LENGTH, "TargetRecvDataSegmentLength", KT_NUMERICAL /* 512 to 2^24 - 1 */, B_FALSE }, { KI_INITIATOR_RECV_DATA_SEGMENT_LENGTH, "InitiatorRecvDataSegmentLength", KT_NUMERICAL /* 512 to 2^24 - 1 */, B_FALSE }, { KI_MAX_OUTSTANDING_UNEXPECTED_PDUS, "MaxOutstandingUnexpectedPDUs", KT_NUMERICAL /* 2 to 2^32 - 1 | 0 */, B_TRUE }, /* * Table terminator. The type KT_TEXT will allow the response * value of "NotUnderstood". */ { 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; } idm_textbuf_t; /* * Ignore all but the following keys during security negotiation * * SessionType * InitiatorName * TargetName * TargetAddress * InitiatorAlias * TargetAlias * TargetPortalGroupTag * AuthMethod and associated auth keys */ static int idm_keyvalue_get_next(char **tb_scan, int *tb_len, char **key, int *keylen, char **value); static int idm_nvlist_add_kv(nvlist_t *nvl, const idm_kv_xlate_t *ikvx, char *value); static int idm_nvlist_add_string(nvlist_t *nvl, const idm_kv_xlate_t *ikvx, char *value); static int idm_nvlist_add_boolean(nvlist_t *nvl, const idm_kv_xlate_t *ikvx, char *value); static int idm_nvlist_add_binary(nvlist_t *nvl, const idm_kv_xlate_t *ikvx, char *value); static int idm_nvlist_add_large_numerical(nvlist_t *nvl, const idm_kv_xlate_t *ikvx, char *value); static int idm_nvlist_add_numerical(nvlist_t *nvl, const idm_kv_xlate_t *ikvx, char *value); static int idm_nvlist_add_numeric_range(nvlist_t *nvl, const idm_kv_xlate_t *ikvx, char *value); static int idm_nvlist_add_list_of_values(nvlist_t *nvl, const idm_kv_xlate_t *ikvx, char *value); static int idm_itextbuf_add_nvpair(nvpair_t *nvp, idm_textbuf_t *itb); static int idm_itextbuf_add_string(nvpair_t *nvp, const idm_kv_xlate_t *ikvx, idm_textbuf_t *itb); static int idm_itextbuf_add_boolean(nvpair_t *nvp, const idm_kv_xlate_t *ikvx, idm_textbuf_t *itb); static int idm_itextbuf_add_binary(nvpair_t *nvp, const idm_kv_xlate_t *ikvx, idm_textbuf_t *itb); static int idm_itextbuf_add_large_numerical(nvpair_t *nvp, const idm_kv_xlate_t *ikvx, idm_textbuf_t *itb); static int idm_itextbuf_add_numerical(nvpair_t *nvp, const idm_kv_xlate_t *ikvx, idm_textbuf_t *itb); static int idm_itextbuf_add_numeric_range(nvpair_t *nvp, const idm_kv_xlate_t *ikvx, idm_textbuf_t *itb); static int idm_itextbuf_add_list_of_values(nvpair_t *nvp, const idm_kv_xlate_t *ikvx, idm_textbuf_t *itb); static void textbuf_memcpy(idm_textbuf_t *itb, void *mem, int mem_len); static void textbuf_strcpy(idm_textbuf_t *itb, char *str); static void textbuf_append_char(idm_textbuf_t *itb, char c); static void textbuf_terminate_kvpair(idm_textbuf_t *itb); static int idm_ascii_to_hex(char *enc_hex_byte, uint8_t *bin_val); static int idm_base16_str_to_binary(char *hstr, int hstr_len, uint8_t *binary, int binary_length); static size_t idm_strcspn(const char *string, const char *charset); static size_t idm_strnlen(const char *str, size_t maxlen); /* * 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 idm_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 = idm_keyvalue_get_next(&tbscan, &tblen, &key, &keylen, &value)) != 0) { /* There was a problem reading the key/value pair */ break; } if ((rc = idm_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 idm_textbuf_to_firstfraglen(void *textbuf, int textbuflen) { return (idm_strnlen(textbuf, textbuflen)); } static int idm_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 = idm_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 = idm_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 idm_kv_xlate_t * idm_lookup_kv_xlate(const char *key, int keylen) { const idm_kv_xlate_t *ikvx = &idm_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 idm_nvlist_add_kv(nvlist_t *nvl, const idm_kv_xlate_t *ikvx, char *value) { int rc; switch (ikvx->ik_idm_type) { case KT_TEXT: case KT_SIMPLE: case KT_ISCSI_NAME: case KT_ISCSI_LOCAL_NAME: rc = idm_nvlist_add_string(nvl, ikvx, value); break; case KT_BOOLEAN: rc = idm_nvlist_add_boolean(nvl, ikvx, value); break; case KT_REGULAR_BINARY: case KT_LARGE_BINARY: case KT_BINARY: rc = idm_nvlist_add_binary(nvl, ikvx, value); break; case KT_LARGE_NUMERICAL: rc = idm_nvlist_add_large_numerical(nvl, ikvx, value); break; case KT_NUMERICAL: rc = idm_nvlist_add_numerical(nvl, ikvx, value); break; case KT_NUMERIC_RANGE: rc = idm_nvlist_add_numeric_range(nvl, ikvx, value); break; case KT_LIST_OF_VALUES: rc = idm_nvlist_add_list_of_values(nvl, ikvx, value); break; default: ASSERT(0); /* This should never happen */ break; } if (rc != 0) { /* could be one of the text constants */ rc = idm_nvlist_add_string(nvl, ikvx, value); } return (rc); } static int idm_nvlist_add_string(nvlist_t *nvl, const idm_kv_xlate_t *ikvx, char *value) { return (nvlist_add_string(nvl, ikvx->ik_key_name, value)); } static int idm_nvlist_add_boolean(nvlist_t *nvl, const idm_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 idm_nvlist_add_binary(nvlist_t *nvl, const idm_kv_xlate_t *ikvx, char *value) { int rc; int value_length; uint64_t uint64_value; int binary_length; 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). 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_SLEEP); if (idm_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_array = kmem_alloc(value_length, KM_NOSLEEP); if (binary_array == NULL) { return (ENOMEM); } if (iscsi_base64_str_to_binary(value, value_length, binary_array, value_length, &binary_length) != 0) { kmem_free(binary_array, value_length); return (EINVAL); } rc = nvlist_add_byte_array(nvl, ikvx->ik_key_name, binary_array, binary_length); kmem_free(binary_array, value_length); 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. */ if ((rc = idm_strtoull(value, NULL, 0, (u_longlong_t *)&uint64_value)) != 0) return (rc); rc = nvlist_add_byte_array(nvl, ikvx->ik_key_name, (uint8_t *)&uint64_value, sizeof (uint64_value)); return (rc); } /* NOTREACHED */ } static int idm_nvlist_add_large_numerical(nvlist_t *nvl, const idm_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 idm_nvlist_add_numerical(nvlist_t *nvl, const idm_kv_xlate_t *ikvx, char *value) { int rc; uint64_t uint64_value; /* * "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 = idm_strtoull(value, NULL, 0, (u_longlong_t *)&uint64_value); if (rc == 0) { rc = nvlist_add_uint64(nvl, ikvx->ik_key_name, uint64_value); } return (rc); } static int idm_nvlist_add_numeric_range(nvlist_t *nvl, const idm_kv_xlate_t *ikvx, char *range) { nvlist_t *range_nvl; char *val_scan = range; uint64_t start_val, end_val; int val_len, range_len; int rc; /* 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 idm_keyvalue_get_next to ensure the string is * terminated */ range_len = strlen(range); /* * Find range separator */ val_len = idm_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 = idm_strtoull(val_scan, NULL, 0, (u_longlong_t *)&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 = idm_strtoull(val_scan, NULL, 0, (u_longlong_t *)&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); } nvlist_free(range_nvl); return (0); } static int idm_nvlist_add_list_of_values(nvlist_t *nvl, const idm_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 idm_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 = idm_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); } nvlist_free(value_list_nvl); 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 idm_nvlist_to_textbuf(nvlist_t *nvl, char **textbuf, int *textbuflen, int *validlen) { int rc = 0; nvpair_t *nvp = NULL; idm_textbuf_t itb; bzero(&itb, sizeof (itb)); for (;;) { nvp = nvlist_next_nvpair(nvl, nvp); if (nvp == NULL) { /* Last nvpair in nvlist, we're done */ break; } if ((rc = idm_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 idm_itextbuf_add_nvpair(nvpair_t *nvp, idm_textbuf_t *itb) { int rc = 0; char *key; const idm_kv_xlate_t *ikvx; key = nvpair_name(nvp); ikvx = idm_lookup_kv_xlate(key, strlen(key)); /* * 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. */ ASSERT((ikvx->ik_key_id != KI_MAX_KEY) || (nvpair_type(nvp) == DATA_TYPE_STRING)); /* * 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_idm_type) { case KT_TEXT: case KT_SIMPLE: case KT_ISCSI_NAME: case KT_ISCSI_LOCAL_NAME: rc = idm_itextbuf_add_string(nvp, ikvx, itb); break; case KT_BOOLEAN: rc = idm_itextbuf_add_boolean(nvp, ikvx, itb); break; case KT_REGULAR_BINARY: case KT_LARGE_BINARY: case KT_BINARY: rc = idm_itextbuf_add_binary(nvp, ikvx, itb); break; case KT_LARGE_NUMERICAL: rc = idm_itextbuf_add_large_numerical(nvp, ikvx, itb); break; case KT_NUMERICAL: rc = idm_itextbuf_add_numerical(nvp, ikvx, itb); break; case KT_NUMERIC_RANGE: rc = idm_itextbuf_add_numeric_range(nvp, ikvx, itb); break; case KT_LIST_OF_VALUES: rc = idm_itextbuf_add_list_of_values(nvp, ikvx, itb); break; default: ASSERT(0); /* This should never happen */ break; } return (rc); } /* ARGSUSED */ static int idm_itextbuf_add_string(nvpair_t *nvp, const idm_kv_xlate_t *ikvx, idm_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); } /* ARGSUSED */ static int idm_itextbuf_add_boolean(nvpair_t *nvp, const idm_kv_xlate_t *ikvx, idm_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); } /* ARGSUSED */ static int idm_itextbuf_add_binary(nvpair_t *nvp, const idm_kv_xlate_t *ikvx, idm_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, idm_hex_to_ascii[(n >> 4) & 0xf]); textbuf_append_char(itb, idm_hex_to_ascii[n & 0xf]); } /* Add trailing 0x00 */ textbuf_terminate_kvpair(itb); return (0); } /* ARGSUSED */ static int idm_itextbuf_add_large_numerical(nvpair_t *nvp, const idm_kv_xlate_t *ikvx, idm_textbuf_t *itb) { ASSERT(0); return (0); } /* ARGSUSED */ static int idm_itextbuf_add_numerical(nvpair_t *nvp, const idm_kv_xlate_t *ikvx, idm_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); (void) sprintf(str, "%llu", (u_longlong_t)value); textbuf_strcpy(itb, str); /* Add trailing 0x00 */ textbuf_terminate_kvpair(itb); return (0); } /* ARGSUSED */ static int idm_itextbuf_add_numeric_range(nvpair_t *nvp, const idm_kv_xlate_t *ikvx, idm_textbuf_t *itb) { ASSERT(0); return (0); } /* ARGSUSED */ static int idm_itextbuf_add_list_of_values(nvpair_t *nvp, const idm_kv_xlate_t *ikvx, idm_textbuf_t *itb) { char *key_name; nvpair_t *vchoice = NULL; char *vchoice_string = NULL; 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 = idm_get_next_listvalue(nvp, NULL); while (vchoice != NULL) { rc = nvpair_value_string(vchoice, &vchoice_string); ASSERT(rc == 0); textbuf_strcpy(itb, vchoice_string); vchoice = idm_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(idm_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(idm_textbuf_t *itb, void *mem, int mem_len) { textbuf_makeroom(itb, mem_len); (void) memcpy(itb->itb_mem + itb->itb_offset, mem, mem_len); itb->itb_offset += mem_len; } static void textbuf_strcpy(idm_textbuf_t *itb, char *str) { textbuf_memcpy(itb, str, strlen(str)); } static void textbuf_append_char(idm_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(idm_textbuf_t *itb) { textbuf_append_char(itb, '\0'); } static int idm_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 idm_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 (idm_ascii_to_hex(tmpstr, binary_scan) != 0) { return (EINVAL); } hstr++; binary_scan++; } while (binary_scan != binary_array + binary_length) { if (idm_ascii_to_hex(hstr, binary_scan) != 0) { return (EINVAL); } hstr += 2; binary_scan++; } return (0); } static size_t idm_strnlen(const char *str, size_t maxlen) { const char *ptr; ptr = memchr(str, 0, maxlen); if (ptr == NULL) return (maxlen); return ((uintptr_t)ptr - (uintptr_t)str); } size_t idm_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 ((uintptr_t)q - (uintptr_t)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 * idm_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); } kv_status_t idm_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 idm_kvstat_to_error(kv_status_t kvrc, uint8_t *class, uint8_t *detail) { switch (kvrc) { case KV_HANDLED: case KV_HANDLED_NO_TRANSIT: *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; } } int idm_nvlist_add_keyvalue(nvlist_t *nvl, char *key, int keylen, char *value) { const idm_kv_xlate_t *ikvx; ikvx = idm_lookup_kv_xlate(key, keylen); if (ikvx->ik_key_id == KI_MAX_KEY) { char *nkey; int rc; size_t len; /* * key is not a NULL terminated string, so create one */ len = (size_t)(keylen+1); nkey = kmem_zalloc(len, KM_SLEEP); (void) strncpy(nkey, key, len-1); rc = nvlist_add_string(nvl, nkey, value); kmem_free(nkey, len); return (rc); } return (idm_nvlist_add_kv(nvl, ikvx, value)); } int idm_nvlist_add_id(nvlist_t *nvl, iscsikey_id_t kv_id, char *value) { int i; for (i = 0; i < KI_MAX_KEY; i++) { if (idm_kvpair_xlate[i].ik_key_id == kv_id) { return (idm_nvlist_add_kv(nvl, &idm_kvpair_xlate[i], value)); } } return (EFAULT); } char * idm_id_to_name(iscsikey_id_t kv_id) { int i; for (i = 0; i < KI_MAX_KEY; i++) { if (idm_kvpair_xlate[i].ik_key_id == kv_id) { return (idm_kvpair_xlate[i].ik_key_name); } } return (NULL); } /* * return the value in a buffer that must be freed by the caller */ char * idm_nvpair_value_to_textbuf(nvpair_t *nvp) { int rv, len; idm_textbuf_t itb; char *str; bzero(&itb, sizeof (itb)); rv = idm_itextbuf_add_nvpair(nvp, &itb); if (rv != 0) return (NULL); str = kmem_alloc(itb.itb_mem_len, KM_SLEEP); len = idm_strcspn(itb.itb_mem, "="); if (len > strlen(itb.itb_mem)) { kmem_free(itb.itb_mem, itb.itb_mem_len); return (NULL); } (void) strcpy(str, &itb.itb_mem[len+1]); /* free the allocation done in idm_textbuf_add_nvpair */ kmem_free(itb.itb_mem, itb.itb_mem_len); return (str); } /* * build an iscsi text buffer - the memory gets freed in * idm_itextbuf_free */ void * idm_nvlist_to_itextbuf(nvlist_t *nvl) { idm_textbuf_t *itb; char *textbuf; int validlen, textbuflen; if (idm_nvlist_to_textbuf(nvl, &textbuf, &textbuflen, &validlen) != IDM_STATUS_SUCCESS) { return (NULL); } itb = kmem_zalloc(sizeof (idm_textbuf_t), KM_SLEEP); ASSERT(itb != NULL); itb->itb_mem = textbuf; itb->itb_mem_len = textbuflen; itb->itb_offset = validlen; 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 * idm_pdu_init_text_data(idm_pdu_t *pdu, void *arg, int max_xfer_len, char *bufptr, int *transit) { char *start_ptr, *end_ptr, *ptr; idm_textbuf_t *itb = arg; iscsi_hdr_t *ihp = pdu->isp_hdr; int send = 0; ASSERT(itb != NULL); ASSERT(pdu != NULL); ASSERT(transit != NULL); if (bufptr == NULL) { /* first call - check the length */ if (itb->itb_offset <= max_xfer_len) { idm_pdu_init_data(pdu, (uint8_t *)itb->itb_mem, itb->itb_offset); ihp->flags &= ~ISCSI_FLAG_TEXT_CONTINUE; *transit = 1; return (NULL); } /* we have more data than will fit in one pdu */ start_ptr = itb->itb_mem; end_ptr = &itb->itb_mem[max_xfer_len - 1]; } else { if ((uintptr_t)&itb->itb_mem[itb->itb_offset] - (uintptr_t)bufptr <= max_xfer_len) { idm_pdu_init_data(pdu, (uint8_t *)bufptr, (uintptr_t)&itb->itb_mem[itb->itb_offset] - (uintptr_t)bufptr); ihp->flags &= ~ISCSI_FLAG_TEXT_CONTINUE; *transit = 1; return (NULL); } /* we have more data then will fit in one pdu */ start_ptr = bufptr; end_ptr = &bufptr[max_xfer_len - 1]; } /* break after key, after =, after the value or after '\0' */ ptr = end_ptr; if (end_ptr + 1 <= &itb->itb_mem[itb->itb_offset]) { /* if next char is an '=' or '\0' send it */ if (*(end_ptr + 1) == '=' || *(end_ptr + 1) == '\0') { send = 1; } } if (!send) { while (*ptr != '\0' && *ptr != '=' && ptr != start_ptr) { ptr--; } } idm_pdu_init_data(pdu, (uint8_t *)start_ptr, ((uintptr_t)ptr - (uintptr_t)start_ptr) + 1); ihp->flags |= ISCSI_FLAG_TEXT_CONTINUE; *transit = 0; return (++ptr); } void idm_itextbuf_free(void *arg) { idm_textbuf_t *itb = arg; ASSERT(itb != NULL); kmem_free(itb->itb_mem, itb->itb_mem_len); kmem_free(itb, sizeof (idm_textbuf_t)); } /* * Allocate an nvlist and poputlate with key=value from the pdu list. * NOTE: caller must free the list */ idm_status_t idm_pdu_list_to_nvlist(list_t *pdu_list, nvlist_t **nvlist, uint8_t *error_detail) { idm_pdu_t *pdu, *next_pdu; boolean_t split_kv = B_FALSE; char *textbuf, *leftover_textbuf = NULL; int textbuflen, leftover_textbuflen = 0; char *split_kvbuf; int split_kvbuflen, cont_fraglen; iscsi_login_hdr_t *lh; int rc; int ret = IDM_STATUS_SUCCESS; *error_detail = ISCSI_LOGIN_STATUS_ACCEPT; /* Allocate a new nvlist for request key/value pairs */ rc = nvlist_alloc(nvlist, NV_UNIQUE_NAME, KM_NOSLEEP); if (rc != 0) { *error_detail = ISCSI_LOGIN_STATUS_NO_RESOURCES; ret = IDM_STATUS_FAIL; 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. */ pdu = list_head(pdu_list); while (pdu != NULL) { next_pdu = list_next(pdu_list, pdu); lh = (iscsi_login_hdr_t *)pdu->isp_hdr; textbuf = (char *)pdu->isp_data; textbuflen = pdu->isp_datalen; if (textbuflen == 0) { /* This shouldn't really happen but it could.. */ list_remove(pdu_list, pdu); idm_pdu_complete(pdu, IDM_STATUS_SUCCESS); pdu = next_pdu; 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) { cont_fraglen = idm_textbuf_to_firstfraglen(textbuf, textbuflen); if (cont_fraglen == pdu->isp_datalen) { /* * This key-value pair spans more than two * PDU's. We don't handle this. */ *error_detail = ISCSI_LOGIN_STATUS_TARGET_ERROR; ret = IDM_STATUS_FAIL; goto cleanup; } split_kvbuflen = leftover_textbuflen + cont_fraglen; split_kvbuf = kmem_alloc(split_kvbuflen, KM_NOSLEEP); if (split_kvbuf == NULL) { *error_detail = ISCSI_LOGIN_STATUS_NO_RESOURCES; ret = IDM_STATUS_FAIL; goto cleanup; } bcopy(leftover_textbuf, split_kvbuf, leftover_textbuflen); bcopy(textbuf, (uint8_t *)split_kvbuf + leftover_textbuflen, cont_fraglen); 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. */ kmem_free(split_kvbuf, split_kvbuflen); *error_detail = ISCSI_LOGIN_STATUS_TARGET_ERROR; ret = IDM_STATUS_FAIL; goto cleanup; } ASSERT(split_kvbuflen != NULL); kmem_free(split_kvbuf, split_kvbuflen); /* Now handle the remainder of the PDU as normal */ textbuf += (cont_fraglen + 1); textbuflen -= (cont_fraglen + 1); } /* * 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) { *error_detail = ISCSI_LOGIN_STATUS_TARGET_ERROR; ret = IDM_STATUS_FAIL; goto cleanup; } ASSERT( ((lh->flags & ISCSI_FLAG_LOGIN_CONTINUE) && (next_pdu != NULL)) || (!(lh->flags & ISCSI_FLAG_LOGIN_CONTINUE) && (next_pdu == NULL))); if ((lh->flags & ISCSI_FLAG_LOGIN_CONTINUE) & (textbuflen != 0)) { /* * Key-value pair is split over two PDU's. We * assume it willl never be split over more than * two PDU's. */ split_kv = B_TRUE; leftover_textbuf = textbuf; leftover_textbuflen = textbuflen; } else { split_kv = B_FALSE; if (textbuflen != 0) { /* * Incomplete keyword but no additional * PDU's. This is a malformed login * request. */ *error_detail = ISCSI_LOGIN_STATUS_INVALID_REQUEST; ret = IDM_STATUS_FAIL; goto cleanup; } } list_remove(pdu_list, pdu); idm_pdu_complete(pdu, IDM_STATUS_SUCCESS); pdu = next_pdu; } cleanup: /* * Free any remaining PDUs on the list. This will only * happen if there were errors encountered during * processing of the textbuf. */ pdu = list_head(pdu_list); while (pdu != NULL) { next_pdu = list_next(pdu_list, pdu); list_remove(pdu_list, pdu); idm_pdu_complete(pdu, IDM_STATUS_SUCCESS); pdu = next_pdu; } /* * 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 isalnum(ch) (isalpha(ch) || isdigit(ch)) #define isalpha(ch) (isupper(ch) || islower(ch)) #define isdigit(ch) ((ch) >= '0' && (ch) <= '9') #define islower(ch) ((ch) >= 'a' && (ch) <= 'z') #define isspace(ch) (((ch) == ' ') || ((ch) == '\r') || ((ch) == '\n') || \ ((ch) == '\t') || ((ch) == '\f')) #define isupper(ch) ((ch) >= 'A' && (ch) <= 'Z') #define isxdigit(ch) (isdigit(ch) || ((ch) >= 'a' && (ch) <= 'f') || \ ((ch) >= 'A' && (ch) <= 'F')) #define DIGIT(x) \ (isdigit(x) ? (x) - '0' : islower(x) ? (x) + 10 - 'a' : (x) + 10 - 'A') #define MBASE ('z' - 'a' + 1 + 10) #define lisalnum(x) \ (isdigit(x) || ((x) >= 'a' && (x) <= 'z') || ((x) >= 'A' && (x) <= 'Z')) #ifndef ULLONG_MAX #define ULLONG_MAX 18446744073709551615ULL #endif int idm_strtoull(const char *str, char **nptr, int base, unsigned long long *result) { unsigned long long val; int c; int xx; unsigned long long multmax; int neg = 0; const char **ptr = (const char **)nptr; const unsigned char *ustr = (const unsigned char *)str; if (ptr != (const char **)0) *ptr = (char *)ustr; /* in case no number is formed */ if (base < 0 || base > MBASE || base == 1) { /* base is invalid -- should be a fatal error */ return (EINVAL); } if (!isalnum(c = *ustr)) { while (isspace(c)) c = *++ustr; switch (c) { case '-': neg++; /* FALLTHROUGH */ case '+': c = *++ustr; } } if (base == 0) if (c != '0') base = 10; else if (ustr[1] == 'x' || ustr[1] == 'X') base = 16; else base = 8; /* * for any base > 10, the digits incrementally following * 9 are assumed to be "abc...z" or "ABC...Z" */ if (!lisalnum(c) || (xx = DIGIT(c)) >= base) return (EINVAL); /* no number formed */ if (base == 16 && c == '0' && (ustr[1] == 'x' || ustr[1] == 'X') && isxdigit(ustr[2])) c = *(ustr += 2); /* skip over leading "0x" or "0X" */ multmax = ULLONG_MAX / (unsigned long long)base; val = DIGIT(c); for (c = *++ustr; lisalnum(c) && (xx = DIGIT(c)) < base; ) { if (val > multmax) goto overflow; val *= base; if (ULONG_MAX - val < xx) goto overflow; val += xx; c = *++ustr; } if (ptr != (const char **)0) *ptr = (char *)ustr; *result = neg ? -val : val; return (0); overflow: for (c = *++ustr; lisalnum(c) && (xx = DIGIT(c)) < base; ) c = *++ustr; if (ptr != (const char **)0) *ptr = (char *)ustr; return (ERANGE); }