diff options
| -rw-r--r-- | .gitlab-ci.yml | 27 | ||||
| -rw-r--r-- | Doxyfile | 54 | ||||
| -rw-r--r-- | src/server/iscsi.c | 192 | ||||
| -rw-r--r-- | src/server/iscsi.h | 9 |
4 files changed, 280 insertions, 2 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..69a5b1c --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,27 @@ +default: + image: debian:trixie-slim + +variables: + GIT_SUBMODULE_STRATEGY: recursive + +build: + stage: build + script: + - apt-get update + - apt-get install -y git doxygen graphviz gcc cmake make libjansson-dev libfuse-dev + - doxygen + artifacts: + paths: + - docs + +pages: + stage: deploy +# only: +# variables: +# - $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH + script: + - rm -rf -- public + - mv -- docs/html public + artifacts: + paths: + - public diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 0000000..aa35fd5 --- /dev/null +++ b/Doxyfile @@ -0,0 +1,54 @@ +# Doxyfile for dnbd3 project + +# Project Info +PROJECT_NAME = dnbd3 +PROJECT_BRIEF = "Documentation for dnbd3 C project" +OUTPUT_DIRECTORY = docs +CREATE_SUBDIRS = NO + +# HTML output +GENERATE_HTML = YES +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html +HTML_COLORSTYLE_HUE = 220 + +# Input +INPUT = src README.md +RECURSIVE = YES +FILE_PATTERNS = *.c *.h CMakeLists.txt *.md + +# Exclude unnecessary stuff (e.g., markdown or docs if not needed) +#EXCLUDE_PATTERNS = */cowDoc/* *.md + +# Source browsing +SOURCE_BROWSER = YES +INLINE_SOURCES = YES +STRIP_CODE_COMMENTS = NO + +# Preprocessor +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = YES +EXPAND_ONLY_PREDEF = NO +SKIP_FUNCTION_MACROS = YES + +# Warnings +WARN_IF_UNDOCUMENTED = YES +WARN_NO_PARAMDOC = YES + +# Dot (call graphs etc.) +HAVE_DOT = YES +CALL_GRAPH = YES +CALLER_GRAPH = YES +DOT_MULTI_TARGETS = YES + +# Other output (disabled) +GENERATE_LATEX = NO +GENERATE_MAN = NO +GENERATE_RTF = NO +GENERATE_XML = NO + +# Misc +EXTRACT_ALL = YES +EXTRACT_PRIVATE = NO +EXTRACT_STATIC = YES +QUIET = NO diff --git a/src/server/iscsi.c b/src/server/iscsi.c index 2dc85cc..7334bd6 100644 --- a/src/server/iscsi.c +++ b/src/server/iscsi.c @@ -387,6 +387,9 @@ void iscsi_hashmap_key_destroy(uint8_t *key) { * be a multiple of 8 bytes which is NOT checked, so * be careful. * @param[in] value Value of the key, NULL is allowed. + * @param[in] user_data This argument is not used by + * this function and should be always NULL for now, as + * there is a possibility for future usage. * @return Always returns 0 as this function cannot fail. */ int iscsi_hashmap_key_destroy_value_callback(uint8_t *key, const size_t key_size, uint8_t *value, uint8_t *user_data) @@ -1093,6 +1096,72 @@ int iscsi_validate_data_digest(const iscsi_bhs_packet *packet_data, const int he } /** + * Validates an iSCSI protocol key and value pair for compliance + * with the iSCSI specs. + * + * @param[in] packet_data Pointer to key / value pair to be + * validated. NULL is an illegal value, so be careful. + * @param[in] len Length of the remaining packet data. + * @return Number of bytes used by the key / vair pair or + * a negative value in case of an error. This can be used for + * incrementing the offset to the next key / value pair. + */ +static int iscsi_validate_text_key_value_pair(const uint8_t *packet_data, const uint32_t len) +{ + const uint key_val_len = strnlen( packet_data, len ); + const uint8_t *key_end = memchr( packet_data, '=', key_val_len ); + + if ( key_end == NULL ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Missing separator '=' for key / value pair -> invalid iSCSI packet data + + const uint key_len = (key_end - packet_data); + + if ( key_len == 0 ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Zero length is not allowed -> invalid iSCSI packet data + + if ( key_len > ISCSI_TEXT_KEY_MAX_LEN ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; + + const uint val_len = strnlen( key_end + 1UL, key_val_len - key_len - 1UL ); + const uint max_len = (memcmp( packet_data, "CHAP_C=", (key_len + 1UL) ) == 0) || (memcmp( packet_data, "CHAP_R=", (key_len + 1UL) ) == 0) ? ISCSI_TEXT_VALUE_MAX_LEN : ISCSI_TEXT_VALUE_MAX_SIMPLE_LEN; + + if ( val_len > max_len ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Value exceeds maximum length -> invalid iSCSI packet data + + return key_len + 1UL + val_len + 1UL; // Number of bytes for processed key / value pair (+1 for '=' and NUL terminator) +} + +/** + * Validates all iSCSI protocol key and value pairs for + * compliance with the iSCSI specs. + * + * @param[in] packet_data Pointer to first key and value pair to + * be validated. NULL is an illegal value here, so be careful. + * @param[in] len Length of the remaining packet data. + * @return 0 if validation for each text key and value pair was + * successful, a negative error code in case iSCSI specs + * are violated. + */ +static int iscsi_validate_key_value_pairs(const uint8_t *packet_data, uint len) +{ + if ( len == 0 ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Zero length is not allowed -> invalid iSCSI packet data + + int offset = 0L; + + while ( (offset < len) && (packet_data[offset] != '\0') ) { + const int rc = iscsi_validate_text_key_value_pair( (packet_data + offset), (len - offset) ); + + if ( rc < ISCSI_VALIDATE_PACKET_RESULT_OK ) + return rc; + + offset += rc; + } + + return (offset != len) ? ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS : ISCSI_VALIDATE_PACKET_RESULT_OK; +} + +/** * Checks whether packet data is an iSCSI packet or not. * Since iSCSI doesn't have a magic identifier for its packets, a * partial heuristic approach is needed for it. There is not always @@ -1180,6 +1249,8 @@ int iscsi_validate_packet(const struct iscsi_bhs_packet *packet_data, const uint if ( (login_req_pkt->reserved != 0) || (login_req_pkt->reserved2[0] != 0) || (login_req_pkt->reserved2[1] != 0) ) return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Reserved fields need all to be zero, but are NOT -> invalid iSCSI packet data + return iscsi_validate_key_value_pairs( ((const uint8_t *) login_req_pkt) + iscsi_align(ds_len, ISCSI_ALIGN_SIZE), ds_len ); + break; } case ISCSI_CLIENT_TEXT_REQ : { @@ -1191,6 +1262,8 @@ int iscsi_validate_packet(const struct iscsi_bhs_packet *packet_data, const uint if ( (text_req_pkt->reserved != 0) || (text_req_pkt->reserved2[0] != 0) || (text_req_pkt->reserved2[1] != 0) ) return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Reserved fields need all to be zero, but are NOT -> invalid iSCSI packet data + return iscsi_validate_key_value_pairs( ((const uint8_t *) text_req_pkt) + iscsi_align(ds_len, ISCSI_ALIGN_SIZE), ds_len ); + break; } case ISCSI_CLIENT_SCSI_DATA_OUT : { @@ -1271,6 +1344,8 @@ int iscsi_validate_packet(const struct iscsi_bhs_packet *packet_data, const uint if ( (login_response_pkt->reserved != 0) || (login_response_pkt->reserved2 != 0) || (login_response_pkt->reserved3 != 0) ) return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Reserved fields need all to be zero, but are NOT -> invalid iSCSI packet data + return iscsi_validate_key_value_pairs( ((const uint8_t *) login_response_pkt) + iscsi_align(ds_len, ISCSI_ALIGN_SIZE), ds_len ); + break; } case ISCSI_SERVER_TEXT_RES : { @@ -1282,6 +1357,8 @@ int iscsi_validate_packet(const struct iscsi_bhs_packet *packet_data, const uint if ( (text_response_pkt->reserved != 0) || (text_response_pkt->reserved2[0] != 0) || (text_response_pkt->reserved2[1] != 0) ) return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Reserved fields need all to be zero, but are NOT -> invalid iSCSI packet data + return iscsi_validate_key_value_pairs( ((const uint8_t *) text_response_pkt) + iscsi_align(ds_len, ISCSI_ALIGN_SIZE), ds_len ); + break; } case ISCSI_SERVER_SCSI_DATA_IN : { @@ -1456,7 +1533,7 @@ static int iscsi_parse_text_key_value_pair(iscsi_hashmap *pairs, const uint8_t * * @param[in] packet_data Pointer to first key and value pair to * be parsed. NULL is an illegal value here, so be careful. * @param[in] len Length of the remaining packet data. - * @param[in] cbit Non-zero value of C bit was set in previously. + * @param[in] c_bit Non-zero value of C bit was set in previously. * @param[in] partial_pairs Array of partial pair pointers in * case C bit was set (multiple iSCSI packets for text data). * @retval -1 An error occured during parsing key. @@ -1538,6 +1615,114 @@ int iscsi_parse_key_value_pairs(iscsi_hashmap **pairs, const uint8_t *packet_dat } /** + * Callback function for constructing an iSCSI packet data + * stream for a single key=value text pair. + * + * @param[in] key Pointer to zero padded key. NULL is + * an invalid pointer here, so be careful. + * @param[in] key_size Number of bytes for the key, MUST + * be a multiple of 8 bytes which is NOT checked, so + * be careful. + * @param[in] value Value of the key, NULL creates an + * empty key assignment. + * @param[in] user_data Pointer to a data structure + * containing the memory buffer and length for the + * iSCSI packet data (iscsi_key_value_pair_packet + * structure), may NOT be NULL, so be careful. + * @retval 0 The NUL terminated key=value pair has been + * generated successfully. + * @retval -1 The NUL terminated key=value pair could + * NOT be created (probably due to memory exhaustion). + */ +int iscsi_create_key_value_pair_packet_callback(uint8_t *key, const size_t key_size, uint8_t *value, uint8_t *user_data) +{ + iscsi_key_value_pair_packet *packet_data = (iscsi_key_value_pair_packet *) user_data; + const uint8_t *buf = iscsi_sprintf_alloc( "%s=%s", key, ((value != NULL) ? value : "") ); + + if ( buf == NULL ) { + logadd( LOG_ERROR, "iscsi_create_key_value_pair_callback: Out of memory generating text key / value pair DataSegment" ); + + return -1L; + } + + const uint len = strlen( buf ) + 1; + const uint new_len = packet_data->len + len; + + uint8_t *new_buf = realloc( packet_data->buf, new_len ); + + if ( new_buf == NULL ) { + logadd( LOG_ERROR, "iscsi_create_key_value_pair_callback: Out of memory generating text key / value pair DataSegment" ); + + free( buf ); + + return -1L; + } + + memcpy( (new_buf + packet_data->len), buf, len ); + + packet_data->buf = new_buf; + packet_data->len = new_len; + + return 0L; +} + +/** + * Creates a properly aligned iSCSI DataSegment + * containing NUL terminated key=value pairs + * out of an hash map. The result can directly + * be attached to a BHS/AHS packet and sent + * via TCP/IP. + * + * @param[in] pairs Pointer to hash map containing + * the key and value pairs to construct the + * DataSegment from, may NOT be NULL, so be careful. + * @return Pointer to iscsi_key_value_pair_packet + * structure containing the properly aligned + * DataSegment buffer and its unaligned length or + * NULL in case of an error (most likely due to + * memory exhaustion). + */ +iscsi_key_value_pair_packet *iscsi_create_key_value_pairs_packet(const iscsi_hashmap *pairs) +{ + iscsi_key_value_pair_packet *packet_data = (iscsi_key_value_pair_packet *) malloc( sizeof(struct iscsi_key_value_pair_packet) ); + + if ( packet_data == NULL ) { + logadd( LOG_ERROR, "iscsi_create_key_value_pairs: Out of memory generating text key / value pair DataSegment" ); + + return NULL; + } + + packet_data->buf = NULL; + packet_data->len = 0UL; + + if ( iscsi_hashmap_iterate( pairs, iscsi_create_key_value_pair_packet_callback, packet_data ) < 0L ) { + if ( packet_data->buf != NULL ) + free( packet_data->buf ); + + free( packet_data ); + + return NULL; + } + + if ( (packet_data->len & (ISCSI_ALIGN_SIZE - 1UL)) != 0 ) { + uint8_t *new_buf = realloc( packet_data->buf, iscsi_align(packet_data->len, ISCSI_ALIGN_SIZE) ); + + if ( new_buf == NULL ) { + logadd( LOG_ERROR, "iscsi_create_key_value_pairs: Out of memory generating text key / value pair DataSegment" ); + + free( packet_data->buf ); + free( packet_data ); + + return NULL; + } + + memset( (packet_data->buf + packet_data->len), 0, (packet_data->len & (ISCSI_ALIGN_SIZE - 1UL)) ); + } + + return packet_data; +} + +/** * Creates a data structure for an incoming iSCSI connection request * from iSCSI packet data. * @@ -1620,7 +1805,10 @@ void iscsi_connection_destroy(iscsi_connection *conn) * be a multiple of 8 bytes which is NOT checked, so * be careful. * @param[in] value Value of the key, NULL is allowed. - * @return Always returns 0a as this function cannot fail. + * @param[in] user_data This argument is not used by + * this function and should be always NULL for now, as + * there is a possibility for future usage. + * @return Always returns 0 as this function cannot fail. */ int iscsi_connection_destroy_callback(uint8_t *key, const size_t key_size, uint8_t *value, uint8_t *user_data) { diff --git a/src/server/iscsi.h b/src/server/iscsi.h index f013a8f..8035d94 100644 --- a/src/server/iscsi.h +++ b/src/server/iscsi.h @@ -2868,6 +2868,8 @@ void iscsi_calc_header_digest(const iscsi_bhs_packet *packet_data); // Calculate int iscsi_validate_header_digest(const iscsi_bhs_packet *packet_data); // Validates a stored iSCSI header digest (CRC32C) with actual header data void iscsi_calc_data_digest(const iscsi_bhs_packet *packet_data, const int header_digest_size); // Calculate iSCSI data digest (CRC32C) int iscsi_validate_data_digest(const iscsi_bhs_packet *packet_data, const int header_digest_size); // Validates a stored iSCSI data digest (CRC32C) with actual DataSegment +static int iscsi_validate_text_key_value_pair(const uint8_t *packet_data, const uint32_t len); // Validates a single text key / value pair according to iSCSI specs +static int iscsi_validate_key_value_pairs(const uint8_t *packet_data, uint len); // Validates all text key / value pairs according to iSCSI specs int iscsi_validate_packet(const struct iscsi_bhs_packet *packet_data, const uint32_t len, const int header_digest_size, const int data_digest_size); // Check if valid iSCSI packet and validate if necessarily #define ISCSI_TEXT_KEY_MAX_LEN 63UL // Maximum length of a key according to iSCSI specs @@ -2891,8 +2893,15 @@ typedef struct iscsi_key_value_pair { uint8_t *value; // Value of key } iscsi_key_value_pair; +typedef struct iscsi_key_value_pair_packet { + uint8_t *buf; // Current text buffer containing multiple zeroes + uint len; // Current length of buffer including final zero terminator +} iscsi_key_value_pair_packet; + static int iscsi_parse_text_key_value_pair(iscsi_hashmap *pairs, const uint8_t *packet_data, const uint32_t len); // Extracts a single text key / value pairs out of an iSCSI packet into a hash map int iscsi_parse_key_value_pairs(iscsi_hashmap **pairs, const uint8_t *packet_data, uint len, int cbit, uint8_t **partial_pair); // Extracts all text key / value pairs out of an iSCSI packet into a hash map +int iscsi_create_key_value_pair_packet_callback(uint8_t *key, const size_t key_size, uint8_t *value, uint8_t *user_data); // Creates a single partial iSCSI packet stream out of a single text key and value pair +iscsi_key_value_pair_packet *iscsi_create_key_value_pairs_packet(const iscsi_hashmap *pairs); // Creates a properly aligned iSCSI packet DataSegment out of a hash map containing text key and value pairs typedef struct iscsi_connection { iscsi_hashmap *key_value_pairs; // Hash map containing text key / value pairs associated to this connection |
