/** * @brief Get a placer pointing to the specified line ('\n' delimited) of content in a data buffer. * @param block a pointer to the block of data to be scanned. * @param length the length, in bytes, of the specified data buffer. * @param number the zero-based index of the line to be retrieved from the buffer. * @return a null placer on failure, or a placer pointing to the specified line on success. */ placer_t line_pl_bl(char *block, size_t length, uint64_t number) { char *start; // We can't search NULL pointers or empty blocks. if (mm_empty(block, length)) { log_pedantic("Passed an invalid parameter."); return pl_null(); } // Keep advancing till we reach the requested line, or the end of the block. while (number && length--) { if (*block++ == '\n') { number--; } } // If we hit the end of the string before finding the requested line, return NULL. if (!length || number) { return pl_null(); } // Advance through until we reach the next new-line character. start = block; while (length-- && *block++ != '\n'); if (*(block - 1) != '\n') { return pl_null(); } return pl_init(start, block - start); }
/** * on error the function returns -1 and sets value to EMPTY_PLACER * on success the function returns 0 and stores the token in the placer pointed at by value * if the end is hit, then the function returns 1, and places any remaining data in value */ int tok_pop(tok_state_t *state, placer_t *value) { char *startPosition; // We can't search NULL pointers or empty strings. if (!value || !state || !state->position) { if (value) { *value = pl_null(); } return -1; } // Handle situations where there is no more data. if (!(state->remaining)) { *value = pl_null(); return 1; } // Handle situations where the pointer is already at the token. if (*(state->position) == state->token) { (state->position)++; (state->remaining)--; *value = pl_null(); if (!(state->remaining)) { return 1; } return 0; } startPosition = state->position; while (*(state->position) != state->token && (state->remaining)) { (state->position)++; (state->remaining)--; } // If we hit the token on the first character, return NULL if ((startPosition) == state->position) { *value = pl_null(); } else { *value = pl_init(startPosition, state->position - startPosition); } // We hit the end of the block if (!(state->remaining)) { return 1; } // Advance to the next character for next time. (state->position)++; (state->remaining)--; return 0; }
/** * @brief Retrieve a specified token from a null-terminated string. * @param string a pointer to the null-terminated string to be tokenized. * @param length the maximum number of characters to be scanned from the input string. * @param token the token character that will be used to split the string. * @param fragment the zero-indexed token number to be extracted from the string. * @param value a pointer to a placer that will receive the value of the extracted token on success, or pl_null() on failure. * @return -1 on failure, 0 on success, or 1 if the token was extracted successfully, but was the last one in the string. */ int tok_get_ns(char *string, size_t length, char token, uint64_t fragment, placer_t *value) { char *start; #ifdef MAGMA_PEDANTIC if (!string) log_pedantic("Attempted token extraction from a NULL string buffer."); else if (!length) log_pedantic("Attempted token extraction from an empty string."); #endif // We can't search NULL pointers or empty strings. if (!value || mm_empty(string, length)) { if (value) { *value = pl_null(); } return -1; } while (fragment && length--) { if (*string++ == token) { fragment--; } } if (fragment) { *value = pl_null(); return 1; } start = string; while (length && *string != token) { string++; length--; } // If we hit the token on the first character, return NULL if (start == string) { *value = pl_null(); } else { *value = pl_init(start, string - start); } // We hit the end of the string if (!length) { return 1; } return 0; }
placer_t imap_parse_address_breaker(stringer_t *address, uint32_t part) { size_t length; int_t status = 0; uint32_t position = 0; placer_t output = pl_null(); chr_t *holder, *start = NULL, *end = NULL; if (address == NULL) { return pl_null(); } length = st_length_get(address); holder = st_char_get(address); while (length > 0 && end == NULL) { if (position == part) { start = holder; position++; } // Inside quotes, nothing counts. if (status == 0 && *holder == '\"') { status = 1; } else if (status == 1 && *holder == '\"') { status = 0; } // Outside quotes, and we hit the break character. else if (status == 0 && position > part && *holder == ',') { end = holder; } else if (status == 0 && position < part && *holder == ',') { position++; } length--; holder++; } // If we hit the end. if (end == NULL) { end = holder; } if (start != NULL && end != NULL) { output = pl_init(start, end - start); } return output; }
/** * @brief Initialize a TLS session for an unauthenticated POP3 session. * @note RFC 2595 / section 4 dictates that the STLS/STARTTLS command should only be available in the authorization state. * @param con the connection of the POP3 client requesting the transport layer security upgrade. * @return This function returns no value (all error messages are written directly to the requesting client). */ void pop_starttls(connection_t *con) { if (con->pop.session_state != 0) { pop_invalid(con); return; } else if (con_secure(con) == 1) { con_write_bl(con, "-ERR Session is already encrypted.\r\n", 36); return; } else if (con_secure(con) == -1) { con_write_bl(con, "-ERR This server has not been configured to support STLS.\r\n", 59); return; } // Tell the user that we are ready to start the negotiation. con_write_bl(con, "+OK Ready to start TLS negotiation.\r\n", 37); if (!(con->network.ssl = ssl_alloc(con->server, con->network.sockd, M_SSL_BIO_NOCLOSE))) { con_write_bl(con, "-ERR STARTTLS FAILED\r\n", 22); log_pedantic("The SSL connection attempt failed."); return; } stats_increment_by_name("pop.connections.secure"); st_length_set(con->network.buffer, 0); con->network.line = pl_null(); con->network.status = 1; pop_session_reset(con); return; }
/** * @brief Retrieve a specified string-split token from a null-terminated string. * @param block a pointer to the block of memory to be tokenized. * @param length the maximum number of characters to be scanned from the input data. * @param token the token string that will be used to split the data. * @param toklen the length, in bytes, of the token string. * @param fragment the zero-indexed token number to be extracted from the data. * @param value a pointer to a placer that will receive the value of the extracted token on success, or pl_null() on failure. * @return -1 on failure, 0 on success, or 1 if the token was extracted successfully, but was the last one in the string. */ int str_tok_get_bl(char *block, size_t length, chr_t *token, size_t toklen, uint64_t fragment, placer_t *value) { placer_t haystack, needle; size_t hptr, skipped = 0; bool_t found; // We can't search NULL pointers or empty strings. if (!value || mm_empty(block, length) || mm_empty(token, toklen)) { *value = pl_null(); return -1; } haystack = pl_init(block, length); needle = pl_init(token, toklen); while (fragment) { if (!(found = st_search_cs(&haystack, &needle, &hptr))) { *value = pl_null(); return -1; } // Haystack becomes the entire block after the token. skipped += pl_length_get (needle) + hptr; haystack = pl_init(pl_char_get(haystack) + skipped, length-skipped); fragment--; } // If no more tokens are present, return everything we have left if (!st_search_cs(&haystack, &needle, &hptr)) { *value = haystack; return 1; } *value = pl_init(pl_char_get(haystack), hptr); return 0; }
/** * @brief Allocate and initialize a new network buffer for a connection, if it is not already associated with one. * @note A network buffer of size magma.system.network_buffer bytes will be allocated for the connection if one does not exist. * @return true if the connection object has been associated with a network buffer or false on failure. */ bool_t con_init_network_buffer(connection_t *con) { if (!con) { return false; } else if (con->network.buffer) { return true; } if (!(con->network.buffer = st_alloc(magma.system.network_buffer))) { log_info("Unable to allocate a network buffer of %u bytes.", magma.system.network_buffer); return false; } con->network.line = pl_null(); return true; }
/** * @brief Read data from a network connection, and store the data in the connection context buffer. * @return -1 on general failure, -2 if the connection was reset, or the amount of data that was read. */ int64_t client_read(client_t *client) { int_t counter = 0; ssize_t bytes = 0; bool_t blocking = true; stringer_t *error = NULL; #ifdef MAGMA_PEDANTIC int_t local = 0; stringer_t *ip = NULL, *cipher = NULL; #endif if (!client || client->sockd == -1 || client_status(client) < 0) { return -1; } // Check for data past the current line buffer. else if (pl_length_get(client->line) && st_length_get(client->buffer) > pl_length_get(client->line)) { // Move the unused data to the front of the buffer. mm_move(st_data_get(client->buffer), st_data_get(client->buffer) + pl_length_get(client->line), st_length_get(client->buffer) - pl_length_get(client->line)); // Update the length. st_length_set(client->buffer, st_length_get(client->buffer) - pl_length_get(client->line)); // Clear the line buffer. client->line = pl_null(); } // Otherwise reset the buffer and line lengths to zero. else { st_length_set(client->buffer, 0); client->line = pl_null(); } // Loop until the buffer has data or we get an error. do { blocking = st_length_get(client->buffer) ? false : true; // Read bytes off the network. If data is already in the buffer this should be a non-blocking read operation so we can // return the already buffered data without delay. if (client->tls) { // If bytes is zero or below and the library isn't asking for another read, then an error occurred. bytes = tls_read(client->tls, st_char_get(client->buffer) + st_length_get(client->buffer), st_avail_get(client->buffer) - st_length_get(client->buffer), blocking); // If zero bytes were read, or a negative value was returned to indicate an error, call tls_erorr(), which will return // NULL if the error can be safely ignored. Otherwise log the output for debug purposes. if (bytes <= 0 && (error = tls_error(client->tls, bytes, MANAGEDBUF(512)))) { #ifdef MAGMA_PEDANTIC cipher = tls_cipher(client->tls, MANAGEDBUF(128)); ip = ip_presentation(client->ip, MANAGEDBUF(INET6_ADDRSTRLEN)); log_pedantic("TLS client read operation failed. { ip = %.*s / %.*s / result = %zi%s%.*s }", st_length_int(ip), st_char_get(ip), st_length_int(cipher), st_char_get(cipher), bytes, (error ? " / " : ""), st_length_int(error), st_char_get(error)); #endif client->status = -1; return -1; } // This will occur when the read operation results in a 0, or negative value, but TLS error returns NULL to // indicate it was a transient error. For transient errors we simply set bytes equal to 0 so the read call gets retried. else if (bytes <= 0) { bytes = 0; } } else { errno = 0; bytes = recv(client->sockd, st_char_get(client->buffer) + st_length_get(client->buffer), st_avail_get(client->buffer) - st_length_get(client->buffer), (blocking ? 0 : MSG_DONTWAIT)); // Check for errors on non-SSL reads in the traditional way. if (bytes <= 0 && tcp_status(client->sockd)) { #ifdef MAGMA_PEDANTIC local = errno; ip = ip_presentation(client->ip, MANAGEDBUF(INET6_ADDRSTRLEN)); log_pedantic("TCP client read operation failed. { ip = %.*s / result = %zi / error = %i / message = %s }", st_length_int(ip), st_char_get(ip), bytes, local, strerror_r(local, MEMORYBUF(1024), 1024)); #endif client->status = -1; return -1; } } // We actually read in data, so we need to update the buffer to reflect the amount of data it currently holds. if (bytes > 0) { st_length_set(client->buffer, st_length_get(client->buffer) + bytes); } } while (blocking && counter++ < 128 && !st_length_get(client->buffer) && status()); // If there is data in the buffer process it. Otherwise if the buffer is empty and the connection appears to be closed // (as indicated by a return value of 0), then return -1 to let the caller know the connection is dead. if (st_length_get(client->buffer)) { client->status = 1; } else if (!bytes) { client->status = 2; return -2; } return st_length_get(client->buffer); }
/** * @brief Read a line of input from a network connection. * @note This function handles reading data from both regular and ssl connections. * This function continually attempts to read incoming data from the specified connection until a \n terminated line of input is received. * If a new line is read, the length of that line is returned to the caller, including the trailing \n. * If the read returns -1 and wasn't caused by a syscall interruption or blocking error, -1 is returned, and the connection status is set to -1. * If the read returns 0 and wasn't caused by a syscall interruption or blocking error, -2 is returned, and the connection status is set to 2. * Once a \n character is reached, the length of the current line of input is returned to the user, and the connection status is set to 1. * @param con the network connection across which the line of data will be read. * @return -1 on general failure, -2 if the connection was reset, or the length of the current line of input, including the trailing new line character. */ int64_t con_read_line(connection_t *con, bool_t block) { ssize_t bytes = 0; int_t counter = 0; bool_t line = false; if (!con || con->network.sockd == -1 || con_status(con) < 0) { if (con) con->network.status = -1; return -1; } // Check for an existing network buffer. If there isn't one, try creating it. else if (!con->network.buffer && !con_init_network_buffer(con)) { con->network.status = -1; return -1; } // Check if we have received more data than just what is in the current line of input. else if (pl_length_get(con->network.line) && st_length_get(con->network.buffer) > pl_length_get(con->network.line)) { // If so, move the unused "new" data after the current line marker to the front of the buffer. mm_move(st_data_get(con->network.buffer), st_data_get(con->network.buffer) + pl_length_get(con->network.line), st_length_get(con->network.buffer) - pl_length_get(con->network.line)); // Update the buffer length. st_length_set(con->network.buffer, st_length_get(con->network.buffer) - pl_length_get(con->network.line)); // Check whether the data we just moved contains a complete line. if (!pl_empty((con->network.line = line_pl_st(con->network.buffer, 0)))) { con->network.status = 1; return pl_length_get(con->network.line); } } // Otherwise reset the buffer and line lengths to zero. else { st_length_set(con->network.buffer, 0); con->network.line = pl_null(); } // Loop until we get a complete line, an error, or the buffer is filled. do { // blocking = st_length_get(con->network.buffer) ? false : true; block = true; if (con->network.tls) { bytes = tls_read(con->network.tls, st_char_get(con->network.buffer) + st_length_get(con->network.buffer), st_avail_get(con->network.buffer) - st_length_get(con->network.buffer), block); } else { bytes = tcp_read(con->network.sockd, st_char_get(con->network.buffer) + st_length_get(con->network.buffer), st_avail_get(con->network.buffer) - st_length_get(con->network.buffer), block); } // We actually read in data, so we need to update the buffer to reflect the amount of unprocessed data it currently holds. if (bytes > 0) { st_length_set(con->network.buffer, st_length_get(con->network.buffer) + bytes); } else if (bytes == 0) { usleep(1000); } else { con->network.status = -1; return -1; } // Check whether we have a complete line before checking whether the connection was closed. if (!st_empty(con->network.buffer) && !pl_empty((con->network.line = line_pl_st(con->network.buffer, 0)))) { line = true; } } while (!line && block && counter++ < 128 && st_length_get(con->network.buffer) != st_avail_get(con->network.buffer) && status()); if (st_length_get(con->network.buffer) > 0) { con->network.status = 1; } return pl_length_get(con->network.line); }
/** * @brief Read a line of input from a network client session. * @return -1 on general failure, -2 if the connection was reset, or the length of the current line of input, including the trailing new line character. */ int64_t client_read_line(client_t *client) { ssize_t bytes = 0; int_t counter = 0; stringer_t *error = NULL; bool_t blocking = true, line = false; #ifdef MAGMA_PEDANTIC int_t local = 0; stringer_t *ip = NULL, *cipher = NULL; #endif if (!client || client->sockd == -1) { if (client) client->status = 1; return -1; } // Check for data past the current line buffer. else if (pl_length_get(client->line) && st_length_get(client->buffer) > pl_length_get(client->line)) { // Move the unused data to the front of the buffer. mm_move(st_data_get(client->buffer), st_data_get(client->buffer) + pl_length_get(client->line), st_length_get(client->buffer) - pl_length_get(client->line)); // Update the length. st_length_set(client->buffer, st_length_get(client->buffer) - pl_length_get(client->line)); // Check whether the data we just moved contains a complete line. if (!pl_empty((client->line = line_pl_st(client->buffer, 0)))) { client->status = 1; return pl_length_get(client->line); } } // Otherwise reset the buffer and line lengths to zero. else { st_length_set(client->buffer, 0); client->line = pl_null(); } // Loop until we get a complete line, an error, or the buffer is filled. do { // Read bytes off the network. Skip past any existing data in the buffer. if (client->tls) { // If bytes is zero or below and the library isn't asking for another read, then an error occurred. bytes = tls_read(client->tls, st_char_get(client->buffer) + st_length_get(client->buffer), st_avail_get(client->buffer) - st_length_get(client->buffer), blocking); // If zero bytes were read, or a negative value was returned to indicate an error, call tls_erorr(), which will return // NULL if the error can be safely ignored. Otherwise log the output for debug purposes. if (bytes <= 0 && (error = tls_error(client->tls, bytes, MANAGEDBUF(512)))) { #ifdef MAGMA_PEDANTIC cipher = tls_cipher(client->tls, MANAGEDBUF(128)); ip = ip_presentation(client->ip, MANAGEDBUF(INET6_ADDRSTRLEN)); log_pedantic("TLS client read operation failed. { ip = %.*s / %.*s / result = %zi%s%.*s }", st_length_int(ip), st_char_get(ip), st_length_int(cipher), st_char_get(cipher), bytes, (error ? " / " : ""), st_length_int(error), st_char_get(error)); #endif client->status = -1; return -1; } // This will occur when the read operation results in a 0, or negative value, but TLS error returns NULL to // indicate it was a transient error. For transient errors we simply set bytes equal to 0 so the read call gets retried. else if (bytes <= 0) { bytes = 0; } } else { errno = 0; bytes = recv(client->sockd, st_char_get(client->buffer) + st_length_get(client->buffer), st_avail_get(client->buffer) - st_length_get(client->buffer), (blocking ? 0 : MSG_DONTWAIT)); // Check for errors on non-SSL reads in the traditional way. if (bytes <= 0 && tcp_status(client->sockd)) { #ifdef MAGMA_PEDANTIC local = errno; ip = ip_presentation(client->ip, MANAGEDBUF(INET6_ADDRSTRLEN)); log_pedantic("TCP client read operation failed. { ip = %.*s / result = %zi / error = %i / message = %s }", st_length_int(ip), st_char_get(ip), bytes, local, strerror_r(local, MEMORYBUF(1024), 1024)); #endif client->status = -1; return -1; } } // We actually read in data, so we need to update the buffer to reflect the amount of data it currently holds. if (bytes > 0) { st_length_set(client->buffer, st_length_get(client->buffer) + bytes); } // Check whether we have a complete line before checking whether the connection was closed. if (!st_empty(client->buffer) && !pl_empty((client->line = line_pl_st(client->buffer, 0)))) { line = true; } } while (!line && counter++ < 128 && st_length_get(client->buffer) != st_avail_get(client->buffer) && status()); if (st_length_get(client->buffer) > 0) { client->status = 1; } return pl_length_get(client->line); }
/** * @brief Read data from a network connection, and store the data in the connection context buffer. * @note This function handles reading data from both regular and SSL connections. * If the connection's network buffer hasn't been allocated, it will be initialized. * @param con a pointer to the connection object from which the data will be read. * @return -1 on general failure, -2 if the connection was reset, or the amount of data that was read. */ int64_t con_read(connection_t *con) { ssize_t bytes = 0; int_t counter = 0; bool_t blocking = true; if (!con || con->network.sockd == -1 || con_status(con) < 0) { if (con) con->network.status = -1; return -1; } // Check for an existing network buffer. If there isn't one, try creating it. else if (!con->network.buffer && !con_init_network_buffer(con)) { con->network.status = -1; return -1; } // Check for data past the current line buffer. else if (pl_length_get(con->network.line) && st_length_get(con->network.buffer) > pl_length_get(con->network.line)) { // Move the unused data to the front of the buffer. mm_move(st_data_get(con->network.buffer), st_data_get(con->network.buffer) + pl_length_get(con->network.line), st_length_get(con->network.buffer) - pl_length_get(con->network.line)); // Update the length. st_length_set(con->network.buffer, st_length_get(con->network.buffer) - pl_length_get(con->network.line)); // Clear the line buffer. con->network.line = pl_null(); if (st_length_get(con->network.buffer)) { return st_length_get(con->network.buffer); } } // Otherwise reset the buffer and line lengths to zero. else { st_length_set(con->network.buffer, 0); con->network.line = pl_null(); } // Loop until the buffer has data or we get an error. do { // blocking = st_length_get(con->network.buffer) ? false : true; blocking = true; if (con->network.tls) { bytes = tls_read(con->network.tls, st_char_get(con->network.buffer) + st_length_get(con->network.buffer), st_avail_get(con->network.buffer) - st_length_get(con->network.buffer), blocking); } else { bytes = tcp_read(con->network.sockd, st_char_get(con->network.buffer) + st_length_get(con->network.buffer), st_avail_get(con->network.buffer) - st_length_get(con->network.buffer), blocking); } // We actually read in data, so we need to update the buffer to reflect the amount of unprocessed data it currently holds. if (bytes > 0) { st_length_set(con->network.buffer, st_length_get(con->network.buffer) + bytes); } else if (bytes == 0) { usleep(1000); } else { con->network.status = -1; return -1; } } while (blocking && counter++ < 128 && !st_length_get(con->network.buffer) && status()); // If there is data in the buffer process it. Otherwise if the buffer is empty and the connection appears to be closed // (as indicated by a return value of 0), then return -1 to let the caller know the connection is dead. if (st_length_get(con->network.buffer)) { con->network.status = 1; } return st_length_get(con->network.buffer); }
/** * @brief Read a line of input from a network client session. * * * @return * * */ int64_t client_read_line(client_t *client) { ssize_t bytes; bool_t line = false; int sslerr; if (!client || client->sockd == -1) { client->status = 1; return -1; } // Check for data past the current line buffer. if (pl_length_get(client->line) && st_length_get(client->buffer) > pl_length_get(client->line)) { // Move the unused data to the front of the buffer. mm_move(st_data_get(client->buffer), st_data_get(client->buffer) + pl_length_get(client->line), st_length_get(client->buffer) - pl_length_get(client->line)); // Update the length. st_length_set(client->buffer, st_length_get(client->buffer) - pl_length_get(client->line)); // Check whether the data we just moved contains a complete line. if (!pl_empty((client->line = line_pl_st(client->buffer, 0)))) { client->status = 1; return pl_length_get(client->line); } } // Otherwise reset the buffer and line lengths to zero. else { st_length_set(client->buffer, 0); client->line = pl_null(); } // Loop until we get a complete line, an error, or the buffer is filled. do { // Read bytes off the network. Skip past any existing data in the buffer. if (client->ssl) { bytes = ssl_read(client->ssl, st_char_get(client->buffer) + st_length_get(client->buffer), st_avail_get(client->buffer) - st_length_get(client->buffer), true); sslerr = SSL_get_error_d(client->ssl, bytes); } else { bytes = recv(client->sockd, st_char_get(client->buffer) + st_length_get(client->buffer), st_avail_get(client->buffer) - st_length_get(client->buffer), 0); } // Check for errors on SSL reads. if (client->ssl) { // If 0 bytes were read, and it wasn't related to a shutdown, or if < 0 was returned and there was no more data waiting, it's an error. if ((!bytes && sslerr != SSL_ERROR_NONE && sslerr != SSL_ERROR_ZERO_RETURN) || ((bytes < 0) && sslerr != SSL_ERROR_WANT_READ)) { client->status = -1; return -1; } } // Check for errors on non-SSL reads in the traditional way. else if (bytes < 0 && errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) { client->status = -1; return -1; } if (bytes > 0) { st_length_set(client->buffer, st_length_get(client->buffer) + bytes); } // Check whether we have a complete line before checking whether the connection was closed. if (!st_empty(client->buffer) && !pl_empty((client->line = line_pl_st(client->buffer, 0)))) { line = true; } // Otherwise if the connection has been closed (as indicated by a return value of 0) the line will never terminate. As such // the best course of action is to return an error code up the stack to indicate the disconnect. else if (!bytes) { client->status = 2; return -2; } } while (status() && !line && st_length_get(client->buffer) != st_avail_get(client->buffer)); if (st_length_get(client->buffer) > 0) { client->status = 1; } return pl_length_get(client->line); }
int64_t client_read(client_t *client) { ssize_t bytes; bool_t blocking; int sslerr; if (!client || client->sockd == -1) { client->status = -1; return -1; } // Check for data past the current line buffer. if (pl_length_get(client->line) && st_length_get(client->buffer) > pl_length_get(client->line)) { // Move the unused data to the front of the buffer. mm_move(st_data_get(client->buffer), st_data_get(client->buffer) + pl_length_get(client->line), st_length_get(client->buffer) - pl_length_get(client->line)); // Update the length. st_length_set(client->buffer, st_length_get(client->buffer) - pl_length_get(client->line)); // Clear the line buffer. client->line = pl_null(); } // Otherwise reset the buffer and line lengths to zero. else { st_length_set(client->buffer, 0); client->line = pl_null(); } // Loop until the buffer has data or we get an error. do { blocking = st_length_get(client->buffer) ? false : true; // Read bytes off the network. If data is already in the buffer this should be a non-blocking read operation so we can // return the already buffered data without delay. if (client->ssl) { bytes = ssl_read(client->ssl, st_char_get(client->buffer) + st_length_get(client->buffer), st_avail_get(client->buffer) - st_length_get(client->buffer), blocking); sslerr = SSL_get_error_d(client->ssl, bytes); } else { bytes = recv(client->sockd, st_char_get(client->buffer) + st_length_get(client->buffer), st_avail_get(client->buffer) - st_length_get(client->buffer), blocking ? 0 : MSG_DONTWAIT); } // Check for errors on SSL reads. if (client->ssl) { // If 0 bytes were read, and it wasn't related to a shutdown, or if < 0 was returned and there was no more data waiting, it's an error. if ((!bytes && sslerr != SSL_ERROR_NONE && sslerr != SSL_ERROR_ZERO_RETURN) || ((bytes < 0) && sslerr != SSL_ERROR_WANT_READ)) { client->status = -1; return -1; } // Check for errors on non-SSL reads in the traditional way. } else if (bytes < 0 && errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) { client->status = -1; return -1; } if (bytes > 0) { st_length_set(client->buffer, st_length_get(client->buffer) + bytes); // Or break out of the loop because we've been shutdown. } else if (!bytes) { break; } } while (status() && blocking && !st_length_get(client->buffer)); // If there is data in the buffer process it. Otherwise if the buffer is empty and the connection appears to be closed // (as indicated by a return value of 0), then return -1 to let the caller know the connection is dead. if (st_length_get(client->buffer)) { client->status = 1; } else if (!bytes) { client->status = 2; return -2; } return st_length_get(client->buffer); }
/** * @brief Read a line of input from a network connection. * @note This function handles reading data from both regular and ssl connections. * This function continually attempts to read incoming data from the specified connection until a \n terminated line of input is received. * If a new line is read, the length of that line is returned to the caller, including the trailing \n. * If the read returns -1 and wasn't caused by a syscall interruption or blocking error, -1 is returned, and the connection status is set to -1. * If the read returns 0 and wasn't caused by a syscall interruption or blocking error, -2 is returned, and the connection status is set to 2. * Once a \n character is reached, the length of the current line of input is returned to the user, and the connection status is set to 1. * @param con the network connection across which the line of data will be read. * @return -1 on general failure, -2 if the connection was reset, or the length of the current line of input, including the trailing \n. */ int64_t con_read_line(connection_t *con, bool_t block) { ssize_t bytes; bool_t line = false; if (!con || con->network.sockd == -1) { con->network.status = -1; return -1; } // Check for an existing network buffer. If there isn't one, try creating it. if (!con->network.buffer && !con_init_network_buffer(con)) { con->network.status = -1; return -1; } // Check if we have received more data than just what is in the current line of input. if (pl_length_get(con->network.line) && st_length_get(con->network.buffer) > pl_length_get(con->network.line)) { // If so, move the unused "new" data after the current line marker to the front of the buffer. mm_move(st_data_get(con->network.buffer), st_data_get(con->network.buffer) + pl_length_get(con->network.line), st_length_get(con->network.buffer) - pl_length_get(con->network.line)); // Update the buffer length. st_length_set(con->network.buffer, st_length_get(con->network.buffer) - pl_length_get(con->network.line)); // Check whether the data we just moved contains a complete line. if (!pl_empty((con->network.line = line_pl_st(con->network.buffer, 0)))) { con->network.status = 1; return pl_length_get(con->network.line); } } // Otherwise reset the buffer and line lengths to zero. else { st_length_set(con->network.buffer, 0); con->network.line = pl_null(); } // Loop until we get a complete line, an error, or the buffer is filled. do { // Read bytes off the network. Skip past any existing data in the buffer. if (con->network.ssl) { // If bytes is zero or below and the library isn't asking for another read, then an error occurred. bytes = ssl_read(con->network.ssl, st_char_get(con->network.buffer) + st_length_get(con->network.buffer), st_avail_get(con->network.buffer) - st_length_get(con->network.buffer), block); if (bytes <= 0 && bytes != SSL_ERROR_WANT_READ) { con->network.status = -1; return -1; } else if (bytes <= 0) { return 0; } } else { bytes = recv(con->network.sockd, st_char_get(con->network.buffer) + st_length_get(con->network.buffer), st_avail_get(con->network.buffer) - st_length_get(con->network.buffer), (block ? 0 : MSG_DONTWAIT)); // Check for errors on non-SSL reads in the traditional way. if (bytes <= 0 && errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) { con->network.status = -1; return -1; } else if (!bytes) { con->network.status = 2; return -2; } } if (bytes > 0) { st_length_set(con->network.buffer, st_length_get(con->network.buffer) + bytes); } // Check whether we have a complete line before checking whether the connection was closed. if (!st_empty(con->network.buffer) && !pl_empty((con->network.line = line_pl_st(con->network.buffer, 0)))) { line = true; } } while (status() && !line && st_length_get(con->network.buffer) != st_avail_get(con->network.buffer)); if (st_length_get(con->network.buffer) > 0) { con->network.status = 1; } return pl_length_get(con->network.line); }
/** * @brief Extract the contents of a literal string and advance the position of the parser stream. * @note This function expects as input a string beginning with '{' and followed by a numerical string, an optional '+', and a closing '}'. After reading in the numerical size parameter, it then attempts to read in that many bytes of input from the network stream. * @param con the client IMAP connection passing the literal string as input to the server. * @param output the address of a managed string that will receive a copy of the literal string's contents on success, or NULL on failure or if it is zero length. * @param start the address of a pointer to the start of the buffer to be parsed (beginning with '{'), that will also be updated to * point to the next argument in the sequence on success. * @param length a pointer to a size_t variable that contains the length of the string to be parsed, and that will be updated to reflect * the length of the remainder of the input string that follows the parsed literal string. * @return -1 on general or parse error or if an enclosing pair of double quotes was not found, or 1 if the supplied quoted string was valid. */ int_t imap_parse_literal(connection_t *con, stringer_t **output, chr_t **start, size_t *length) { chr_t *holder; int_t plus = 0; stringer_t *result; size_t characters, left; ssize_t nread; uint64_t literal, number; // Get setup. holder = *start; left = *length; *output = NULL; // Skip the opening bracket. if (*holder != '{' || !left) { return -1; } else { holder++; left--; } // Advance until we have a break character. while (left && *holder >= '0' && *holder <= '9') { holder++; left--; } // Store the length. characters = holder - *start - 1; if (left && *holder == '+') { plus = 1; holder++; left--; } if (*holder != '}' || !characters) { return -1; } // Convert to a number. Make sure the number is positive. if (!uint64_conv_bl(*start + 1, characters, &number)) { return -1; } literal = (size_t)number; // If the number is larger than 128 megabytes, then reject it. if (!plus && number > 134217728) { return -1; } // They client is already transmitting, so read the entire file, then reject it. else if (number > 134217728) { while (number > 0) { // Read the data. if ((nread = con_read(con)) <= 0) { log_pedantic("The connection was dropped while reading the literal."); return -1; } // Deal with signedness problem. characters = nread; if (number > (uint64_t)characters) { number -= characters; } else { // If we have any extra characters in the buffer, move them to the beginning. if ((uint64_t)characters > number) { mm_move(st_char_get(con->network.buffer), st_char_get(con->network.buffer) + number, characters - number); st_length_set(con->network.buffer, characters - number); con->network.line = line_pl_st(con->network.buffer, 0); } else { st_length_set(con->network.buffer, 0); con->network.line = pl_null(); } // Make sure we have a full line. if (pl_empty(con->network.line) && con_read_line(con, true) <= 0) { log_pedantic("The connection was dropped while reading the literal."); return -1; } number = 0; } } return -1; } // If this is not a plus literal, output the proceed statement. if (!plus) { con_write_bl(con, "+ GO\r\n", 6); } // Handle the special case of a zero length literal. if (literal == 0) { // Read the next line. if (con_read_line(con, true) <= 0) { log_pedantic("The connection was dropped while reading the literal."); return -1; } *start = st_char_get(con->network.buffer); *length = pl_length_get(con->network.line); // There should be a space before the next argument. if (*length && **start == ' ') { (*start)++; (*length)--; } return 1; } // Allocate a stringer for the buffer. if (!(result = st_alloc(literal))) { log_pedantic("Unable to allocate a buffer of %lu bytes for the literal argument.", literal); return -1; } // So we know how many more characters to read. left = literal; // Where we put the data. holder = st_char_get(result); // Keep looping until we run out of data. while (left) { // Read the data. if ((nread = con_read(con)) <= 0) { log_pedantic("The connection was dropped while reading the literal."); st_free(result); return -1; } characters = nread; // If we have a buffer, copy the data into the buffer. mm_copy(holder, st_char_get(con->network.buffer), (left > characters) ? characters : left); if (left > characters) { holder += characters; left -= characters; } else { st_length_set(result, literal); // If we have any extra characters in the buffer, move them to the beginning. if (characters > left) { mm_move(st_char_get(con->network.buffer), st_char_get(con->network.buffer) + left, characters - left); st_length_set(con->network.buffer, characters - left); con->network.line = line_pl_st(con->network.buffer, 0); } else { st_length_set(con->network.buffer, 0); con->network.line = pl_null(); } // Make sure we have a full line. if (pl_empty(con->network.line) && con_read_line(con, true) <= 0) { log_pedantic("The connection was dropped while reading the literal."); st_free(result); return -1; } left = 0; } } *start = st_char_get(con->network.buffer); *length = pl_length_get(con->network.line); // There should be a space before the next argument. if (*length && **start == ' ') { (*start)++; (*length)--; } if (result != NULL) { *output = result; } else { return -1; } return 1; }
/** * @brief Get a placer pointing to the specified child inside a MIME body. * @param body a placer containing the body text to be parsed. * @param boundary a pointer to a managed string containing the boundary string to split the MIME content. * @param child the zero-based index of the MIME child to be located in the body text. * @return pl_null() on failure, or a placer containing the specified MIME child on success. */ placer_t mail_mime_child(placer_t body, stringer_t *boundary, uint32_t child) { uint32_t result = 0; chr_t *start, *stream, *bounddata; size_t increment = 0, length, boundlen; if (pl_empty(body) || st_empty(boundary)) { return pl_null(); } // Figure out the lengths. if (!(length = st_length_get(&body))) { log_pedantic("Cannot parse children from zero-length MIME body.."); return pl_null(); } else if (!(boundlen = st_length_get(boundary))) { log_pedantic("Cannot parse children from MIME body with zero-length boundary."); return pl_null();; } // Setup. stream = st_char_get(&body); bounddata = st_char_get(boundary); // Find the start of the first part. while (increment + boundlen <= length && result < child) { if (mm_cmp_cs_eq(stream, bounddata, boundlen) == 0 && (increment + boundlen == length || *(stream + boundlen) < '!' || *(stream + boundlen) > '~')) { stream += boundlen; increment += boundlen; // Two dashes indicate the end of this mime sections. if (increment < length && mm_cmp_cs_eq(stream, "--", 2) == 0) { increment = length + 1; } else { result++; } } else { stream++; increment++; } } // The requested child wasn't found. if (increment + boundlen >= length) { return pl_null(); } // This will skip a line break after the boundary marker. if (length - increment > 0 && *stream == '\r') { stream++; increment++; } if (length - increment > 0 && *stream == '\n') { stream++; increment++; } // Store the start position. start = stream; // Find the end. while (increment < length) { if (increment + boundlen < length && mm_cmp_cs_eq(stream, bounddata, boundlen) == 0) { increment = length; } else { stream++; increment++; } } // Make sure we advanced. if (stream == start) { return pl_null(); } return pl_init(start, stream - start); }
/** * @brief Read data from a connection, and store it in its internal buffer. * @note This function handles reading data from both regular and ssl connections. * If the connection's network buffer hasn't been allocated, it will be initialized. * @param con a pointer to the connection object from which the data will be read. * @return -1 on internal error, or on a read error. * * * */ int64_t con_read(connection_t *con) { ssize_t bytes; bool_t blocking; if (!con || con->network.sockd == -1) { con->network.status = -1; return -1; } // Check for an existing network buffer. If there isn't one, try creating it. if (!con->network.buffer && !con_init_network_buffer(con)) { con->network.status = -1; return -1; } // Check for data past the current line buffer. if (pl_length_get(con->network.line) && st_length_get(con->network.buffer) > pl_length_get(con->network.line)) { // Move the unused data to the front of the buffer. mm_move(st_data_get(con->network.buffer), st_data_get(con->network.buffer) + pl_length_get(con->network.line), st_length_get(con->network.buffer) - pl_length_get(con->network.line)); // Update the length. st_length_set(con->network.buffer, st_length_get(con->network.buffer) - pl_length_get(con->network.line)); // Clear the line buffer. con->network.line = pl_null(); } // Otherwise reset the buffer and line lengths to zero. else { st_length_set(con->network.buffer, 0); con->network.line = pl_null(); } // Loop until the buffer has data or we get an error. do { blocking = st_length_get(con->network.buffer) ? false : true; // Read bytes off the network. If data is already in the buffer this should be a non-blocking read operation so we can // return the already buffered data without delay. if (con->network.ssl) { bytes = ssl_read(con->network.ssl, st_char_get(con->network.buffer) + st_length_get(con->network.buffer), st_avail_get(con->network.buffer) - st_length_get(con->network.buffer), blocking); if (!bytes && ssl_shutdown_get(con->network.ssl)) { con->network.status = -1; return -1; } } else { bytes = recv(con->network.sockd, st_char_get(con->network.buffer) + st_length_get(con->network.buffer), st_avail_get(con->network.buffer) - st_length_get(con->network.buffer), blocking ? 0 : MSG_DONTWAIT); if (bytes < 0 && errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) { con->network.status = -1; return -1; } } if (bytes > 0) { st_length_set(con->network.buffer, st_length_get(con->network.buffer) + bytes); // Or break out of the loop because we've been shutdown. } else if (!bytes) { break; } } while (status() && blocking && !st_length_get(con->network.buffer)); // If there is data in the buffer process it. Otherwise if the buffer is empty and the connection appears to be closed // (as indicated by a return value of 0), then return -1 to let the caller know the connection is dead. if (st_length_get(con->network.buffer)) { con->network.status = 1; } else if (!bytes) { con->network.status = 2; return -2; } return st_length_get(con->network.buffer); }