/* * ModSecurity, http://www.modsecurity.org/ * Copyright (c) 2015 Trustwave Holdings, Inc. (http://www.trustwave.com/) * * You may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * If any of the files related to licensing are missing or if you have any * other questions related to licensing please contact Trustwave Holdings, Inc. * directly using the email address security@modsecurity.org. * */ #include "modsecurity/assay.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "modsecurity/modsecurity.h" #include "modsecurity/intervention.h" #include "actions/action.h" #include "actions/deny.h" #include "src/utils.h" #include "src/audit_log.h" #include "src/unique_id.h" #include "request_body_processor/multipart.h" using ModSecurity::actions::Action; using ModSecurity::RequestBodyProcessor::Multipart; namespace ModSecurity { /** * @name Assay * @brief Represents the inspection on an entire request. * * An instance of the Assay class represents an entire request, on its * different phases. * * @note Remember to cleanup the assay when the transaction is complete. * * @param ms ModSecurity core pointer. * @param rules Rules pointer. * * Example Usage: * @code * * using ModSecurity::ModSecurity; * using ModSecurity::Rules; * using ModSecurity::Assay; * * ModSecurity *modsec; * ModSecurity::Rules *rules; * * modsec = new ModSecurity(); * rules = new Rules(); * rules->loadFromUri(rules_file); * * Assay *modsecAssay = new Assay(modsec, rules); * modsecAssay->processConnection("127.0.0.1", 33333, "127.0.0.1", 8080); * * if (modsecAssay->intervention()) { * std::cout << "There is an intervention" << std::endl; * } * * ... * * @endcode * */ Assay::Assay(ModSecurity *ms, Rules *rules, void *logCbData) : m_clientIpAddress(""), m_serverIpAddress(""), m_clientPort(0), m_serverPort(0), m_uri(""), m_protocol(""), m_httpVersion(""), m_rules(rules), save_in_auditlog(false), do_not_save_in_auditlog(false), timeStamp(std::time(NULL)), httpCodeReturned(200), highest_severity(255), m_ARGScombinedSize(0), m_ARGScombinedSizeStr(NULL), m_namesArgs(NULL), m_namesArgsPost(NULL), m_namesArgsGet(NULL), m_requestBodyType(UnknownFormat), m_requestHeadersNames(NULL), m_responseHeadersNames(NULL), m_marker(""), start(cpu_seconds()), m_logCbData(logCbData), m_ms(ms) { id = std::to_string(this->timeStamp) + \ std::to_string(generate_assay_unique_id()); m_rules->incrementReferenceCount(); store_variable("ARGS_COMBINED_SIZE", std::string("0")); this->m_ARGScombinedSizeStr = resolve_variable_first("ARGS_COMBINED_SIZE"); store_variable("ARGS_NAMES", std::string("")); this->m_namesArgs = resolve_variable_first("ARGS_NAMES"); store_variable("ARGS_POST_NAMES", std::string("")); this->m_namesArgsPost = resolve_variable_first("ARGS_POST_NAMES"); store_variable("ARGS_GET_NAMES", std::string("")); this->m_namesArgsGet = resolve_variable_first("ARGS_GET_NAMES"); store_variable("REQUEST_HEADERS_NAMES", std::string("")); this->m_requestHeadersNames = resolve_variable_first( "REQUEST_HEADERS_NAMES"); store_variable("RESPONSE_HEADERS_NAMES", std::string("")); this->m_responseHeadersNames = resolve_variable_first( "RESPONSE_HEADERS_NAMES"); collections.emplace("TX", new ModSecurityStringVariables()); #ifndef NO_LOGS this->debug(4, "Initialising transaction"); #endif } Assay::~Assay() { m_responseBody.str(std::string()); m_responseBody.clear(); m_requestBody.str(std::string()); m_requestBody.clear(); for (auto &a : collections) { delete a.second; } m_rules->decrementReferenceCount(); } /** * @name debug * @brief Prints a message on the debug logs. * * Debug logs are important during the rules creation phase, this method can be * used to print message on this debug log. * * @param level Debug level, current supported from 0 to 9. * @param message Message to be logged. * */ #ifndef NO_LOGS void Assay::debug(int level, std::string message) { if (m_rules == NULL) { return; } m_rules->debug(level, message); } #endif /** * @name processConnection * @brief Perform the analysis on the connection. * * This method should be called at very beginning of a request process, it is * expected to be executed prior to the virtual host resolution, when the * connection arrives on the server. * * @note Remember to check for a possible intervention. * * @param assay ModSecurity assay. * @param client Client's IP address in text format. * @param cPort Client's port * @param server Server's IP address in text format. * @param sPort Server's port * * @returns If the operation was successful or not. * @retval true Operation was successful. * @retval false Operation failed. * */ int Assay::processConnection(const char *client, int cPort, const char *server, int sPort) { this->m_clientIpAddress = client; this->m_serverIpAddress = server; this->m_clientPort = cPort; this->m_serverPort = sPort; #ifndef NO_LOGS debug(4, "Transaction context created."); debug(4, "Starting phase CONNECTION. (SecRules 0)"); #endif this->store_variable("REMOTE_HOST", m_clientIpAddress); this->store_variable("UNIQUE_ID", id); this->store_variable("REMOTE_ADDR", m_clientIpAddress); this->store_variable("SERVER_ADDR", m_serverIpAddress); this->store_variable("SERVER_PORT", std::to_string(this->m_serverPort)); this->store_variable("REMOTE_PORT", std::to_string(this->m_clientPort)); this->m_rules->evaluate(ModSecurity::ConnectionPhase, this); return true; } /** * @name processURI * @brief Perform the analysis on the URI and all the query string variables. * * This method should be called at very beginning of a request process, it is * expected to be executed prior to the virtual host resolution, when the * connection arrives on the server. * * @note There is no direct connection between this function and any phase of * the SecLanguage's phases. It is something that may occur between the * SecLanguage phase 1 and 2. * @note Remember to check for a possible intervention. * * @param assay ModSecurity assay. * @param uri Uri. * @param protocol Protocol (GET, POST, PUT). * @param http_version Http version (1.0, 1.2, 2.0). * * @returns If the operation was successful or not. * @retval true Operation was successful. * @retval false Operation failed. * */ int Assay::processURI(const char *uri, const char *protocol, const char *http_version) { #ifndef NO_LOGS debug(4, "Starting phase URI. (SecRules 0 + 1/2)"); #endif m_protocol = protocol; m_httpVersion = http_version; m_uri = uri; std::string uri_s(uri); m_uri_decoded = uri_decode(uri); size_t pos = m_uri_decoded.find("?"); size_t pos_raw = uri_s.find("?"); store_variable("REQUEST_LINE", std::string(protocol) + " " + std::string(uri) + " HTTP/" + std::string(http_version)); if (pos_raw != std::string::npos) { store_variable("QUERY_STRING", std::string(uri_s, pos_raw + 1, uri_s.length() - (pos_raw + 1))); } std::string path_info; if (pos == std::string::npos) { path_info = std::string(m_uri_decoded, 0); } else { path_info = std::string(m_uri_decoded, 0, pos); } store_variable("PATH_INFO", path_info); store_variable("REQUEST_FILENAME", path_info); size_t offset = path_info.find_last_of("/\\"); if (offset != std::string::npos) { std::string basename = std::string(path_info, offset, path_info.length() - offset); store_variable("REQUEST_BASENAME", basename); } store_variable("REQUEST_METHOD", protocol); store_variable("REQUEST_PROTOCOL", "HTTP/" + std::string(http_version)); store_variable("REQUEST_URI", uri); store_variable("REQUEST_URI_RAW", uri); if (pos != std::string::npos && (m_uri_decoded.length() - pos) > 2) { /** * FIXME: * * This is configurable by secrules, we should respect whatever * the secrules said about it. * */ char sep1 = '&'; std::string sets(m_uri_decoded, pos + 1, m_uri_decoded.length() - (pos + 1)); std::vector key_value_sets = split(sets, sep1); for (std::string t : key_value_sets) { /** * FIXME: * * Mimic modsecurity when there are multiple keys with the same name. * */ char sep2 = '='; std::vector key_value = split(t, sep2); if (key_value.size() <= 1) { /** TODO: Verify what ModSecurity 2.9.0 does when there is a * key without an argument */ continue; } std::string key = key_value[0]; std::string value = key_value[1]; int i = key_value.size() - 1; while (i > 2) { value = value + sep2 + key_value[i]; i--; } store_variable("ARGS:" + key, value); store_variable("ARGS_GET:" + key, value); if (m_namesArgs->empty()) { m_namesArgs->assign(key); } else { m_namesArgs->assign(*m_namesArgs + " " + key); } if (m_namesArgsGet->empty()) { m_namesArgsGet->assign(key); } else { m_namesArgsGet->assign(*m_namesArgsGet + " " + key); } this->m_ARGScombinedSize = this->m_ARGScombinedSize + \ key.length() + value.length(); this->m_ARGScombinedSizeStr->assign( std::to_string(this->m_ARGScombinedSize)); #ifndef NO_LOGS debug(4, "Adding request argument (QUERY_STRING): name \"" + \ key + "\", value \"" + value + "\""); #endif } } return true; } /** * @name processRequestHeaders * @brief Perform the analysis on the request readers. * * This method perform the analysis on the request headers, notice however * that the headers should be added prior to the execution of this function. * * @note Remember to check for a possible intervention. * * @returns If the operation was successful or not. * @retval true Operation was successful. * @retval false Operation failed. * */ int Assay::processRequestHeaders() { #ifndef NO_LOGS debug(4, "Starting phase REQUEST_HEADERS. (SecRules 1)"); #endif if (m_rules->secRuleEngine == Rules::DisabledRuleEngine) { #ifndef NO_LOGS debug(4, "Rule engine disabled, returning..."); #endif return true; } this->m_rules->evaluate(ModSecurity::RequestHeadersPhase, this); return true; } /** * @name addRequestHeader * @brief Adds a request header * * With this method it is possible to feed ModSecurity with a request header. * * @note This function expects a NULL terminated string, for both: key and * value. * * @param key header name. * @param value header value. * * @returns If the operation was successful or not. * @retval true Operation was successful. * @retval false Operation failed. * */ int Assay::addRequestHeader(const std::string& key, const std::string& value) { m_requestHeadersNames->assign(*m_requestHeadersNames + " " + key); this->store_variable("REQUEST_HEADERS:" + key, value); if (tolower(key) == tolower("Authorization")) { std::vector type = split(value, ' '); this->store_variable("AUTH_TYPE", type[0]); } if (tolower(key) == "cookie") { std::vector cookies = split(value, ';'); while (cookies.empty() == false) { std::vector s = split(cookies.back(), '='); if (s.size() > 1) { if (s[0].at(0) == ' ') { s[0].erase(0, 1); } this->store_variable("REQUEST_COOKIES:" + s[0], s[1]); this->store_variable("REQUEST_COOKIES_NAMES:" + s[0], s[0]); } cookies.pop_back(); } } /** * Simple check to decide the request body content. This is not the right * place, the "body processor" should be able to tell what he is capable * to deal with. * */ if (tolower(key) == "content-type") { std::string multipart("multipart/form-data"); std::string l = tolower(value); if (l.compare(0, multipart.length(), multipart) == 0) { this->m_requestBodyType = MultiPartRequestBody; } if (l == "application/x-www-form-urlencoded") { this->m_requestBodyType = WWWFormUrlEncoded; } } return 1; } /** * @name addRequestHeader * @brief Adds a request header * * With this method it is possible to feed ModSecurity with a request header. * * @note This function expects a NULL terminated string, for both: key and * value. * * @param key header name. * @param value header value. * * @returns If the operation was successful or not. * @retval true Operation was successful. * @retval false Operation failed. * */ int Assay::addRequestHeader(const unsigned char *key, const unsigned char *value) { return this->addRequestHeader(key, strlen(reinterpret_cast(key)), value, strlen(reinterpret_cast(value))); } /** * @name addRequestHeader * @brief Adds a request header * * Do not expect a NULL terminated string, instead it expect the string and the * string size, for the value and key. * * @param assay ModSecurity assay. * @param key header name. * @param key_n header name size. * @param value header value. * @param value_n header value size. * * @returns If the operation was successful or not. * @retval 1 Operation was successful. * @retval 0 Operation failed. * */ int Assay::addRequestHeader(const unsigned char *key, size_t key_n, const unsigned char *value, size_t value_n) { std::string keys; std::string values; keys.assign(reinterpret_cast(key), key_n); values.assign(reinterpret_cast(value), value_n); return this->addRequestHeader(keys, values); } /** * @name processRequestBody * @brief Perform the request body (if any) * * This method perform the analysis on the request body. It is optional to * call that function. If this API consumer already know that there isn't a * body for inspect it is recommended to skip this step. * * @note It is necessary to "append" the request body prior to the execution * of this function. * @note Remember to check for a possible intervention. * * @returns If the operation was successful or not. * @retval true Operation was successful. * @retval false Operation failed. * */ int Assay::processRequestBody() { #ifndef NO_LOGS debug(4, "Starting phase REQUEST_BODY. (SecRules 2)"); #endif if (m_rules->secRuleEngine == Rules::DisabledRuleEngine) { #ifndef NO_LOGS debug(4, "Rule engine disabled, returning..."); #endif return true; } if (resolve_variable_first("INBOUND_DATA_ERROR") == NULL) { store_variable("INBOUND_DATA_ERROR", "0"); } /* * Process the request body even if there is nothing to be done. * * if (m_requestBody.tellp() <= 0) { * return true; * } * */ if (m_requestBodyType == MultiPartRequestBody) { std::string *a = resolve_variable_first("REQUEST_HEADERS:Content-Type"); if (a != NULL) { Multipart m(*a, this); if (m.init() == true) { m.process(m_requestBody.str()); for (auto &a : m.variables) { store_variable(a.first, a.second); } if (m.crlf && m.lf) { store_variable("MULTIPART_CRLF_LF_LINES", "1"); } else { store_variable("MULTIPART_CRLF_LF_LINES", "0"); } if (m.boundaryStartsWithWhiteSpace) { #ifndef NO_LOGS debug(9, "Multipart: Boundary starts with white space, " \ "setting MULTIPART_STRICT_ERROR to 1"); #endif update_variable_first("MULTIPART_STRICT_ERROR", "1"); } if (m.boundaryIsQuoted) { #ifndef NO_LOGS debug(9, "Multipart: Boundary is quoted, " \ "setting MULTIPART_STRICT_ERROR to 1"); #endif update_variable_first("MULTIPART_STRICT_ERROR", "1"); } if (m.containsDataAfter) { #ifndef NO_LOGS debug(9, "Multipart: There is data after the boundary, " \ "setting MULTIPART_STRICT_ERROR to 1"); #endif update_variable_first("MULTIPART_STRICT_ERROR", "1"); store_variable("MULTIPART_UNMATCHED_BOUNDARY", "1"); } else { store_variable("MULTIPART_UNMATCHED_BOUNDARY", "0"); } if (m.containsDataBefore) { #ifndef NO_LOGS debug(9, "Multipart: There is data before the boundary, " \ "setting MULTIPART_STRICT_ERROR to 1"); #endif update_variable_first("MULTIPART_STRICT_ERROR", "1"); } if (m.lf) { #ifndef NO_LOGS debug(9, "Multipart: Lines are LF-terminated, " \ "setting MULTIPART_STRICT_ERROR to 1"); #endif update_variable_first("MULTIPART_STRICT_ERROR", "1"); } if (m.missingSemicolon) { #ifndef NO_LOGS debug(9, "Multipart: Boundary missing semicolon, " \ "setting MULTIPART_STRICT_ERROR to 1"); #endif update_variable_first("MULTIPART_STRICT_ERROR", "1"); } if (m.invalidQuote) { #ifndef NO_LOGS debug(9, "Multipart: Invalid quote, " \ "setting MULTIPART_STRICT_ERROR to 1"); #endif update_variable_first("MULTIPART_STRICT_ERROR", "1"); } } } } if (m_requestBodyType == WWWFormUrlEncoded) { std::string content = uri_decode(m_requestBody.str()); if (content.empty() == false) { content.pop_back(); } /** * FIXME: * * This is configurable by secrules, we should respect whatever * the secrules said about it. * */ char sep1 = '&'; std::vector key_value = split(content.c_str(), sep1); for (std::string t : key_value) { /** * FIXME: * * Mimic modsecurity when there are multiple keys with the same name. * */ char sep2 = '='; std::vector key_value = split(t, sep2); store_variable("ARGS:" + key_value[0], key_value[1]); store_variable("ARGS_POST:" + key_value[0], key_value[1]); if (m_namesArgs->empty()) { m_namesArgs->assign(key_value[0]); } else { m_namesArgs->assign(*m_namesArgs + " " + key_value[0]); } if (m_namesArgsPost->empty()) { m_namesArgsPost->assign(key_value[0]); } else { m_namesArgsPost->assign(*m_namesArgsPost + " " + key_value[0]); } this->m_ARGScombinedSize = this->m_ARGScombinedSize + \ key_value[0].length() + key_value[1].length(); this->m_ARGScombinedSizeStr->assign( std::to_string(this->m_ARGScombinedSize)); } } /** * FIXME: This variable should be calculated on demand, it is * computationally intensive. */ std::string fullRequest; std::list l; resolve_variable("REQUEST_HEADERS", &l); for (auto &a : l) { fullRequest = fullRequest + \ std::string(a->m_key, 16, a->m_key.length() - 16) + ": " \ + a->m_value + "\n"; } fullRequest = fullRequest + "\n\n"; fullRequest = fullRequest + m_requestBody.str(); store_variable("FULL_REQUEST", fullRequest); store_variable("FULL_REQUEST_LENGTH", std::to_string(fullRequest.size())); if (m_requestBody.tellp() > 0) { store_variable("REQUEST_BODY", m_requestBody.str()); store_variable("REQUEST_BODY_LENGTH", std::to_string(m_requestBody.str().size())); } this->m_rules->evaluate(ModSecurity::RequestBodyPhase, this); return true; } /** * @name appendRequestBody * @brief Adds request body to be inspected. * * With this method it is possible to feed ModSecurity with data for * inspection regarding the request body. There are two possibilities here: * * 1 - Adds the buffer in a row; * 2 - Adds it in chunks; * * A third option should be developed which is share your application buffer. * In any case, remember that the utilization of this function may reduce your * server throughput, as this buffer creations is computationally expensive. * * @note While feeding ModSecurity remember to keep checking if there is an * intervention, Sec Language has the capability to set the maximum * inspection size which may be reached, and the decision on what to do * in this case is upon the rules. * * @returns If the operation was successful or not. * @retval true Operation was successful. * @retval false Operation failed. * */ int Assay::requestBodyFromFile(const char *path) { std::ifstream request_body(path); std::string str; if (request_body.is_open() == false) { #ifndef NO_LOGS debug(3, "Failed to open request body at: " + std::string(path)); #endif return false; } request_body.seekg(0, std::ios::end); str.reserve(request_body.tellg()); request_body.seekg(0, std::ios::beg); str.assign((std::istreambuf_iterator(request_body)), std::istreambuf_iterator()); const char *buf = str.c_str(); int len = request_body.tellg(); #ifndef NO_LOGS debug(9, "Adding request body: " + std::to_string(len) + " bytes. " \ "Limit set to: " + std::to_string(this->m_rules->requestBodyLimit)); #endif return appendRequestBody(reinterpret_cast(buf), len); } int Assay::appendRequestBody(const unsigned char *buf, size_t len) { int current_size = this->m_requestBody.tellp(); #ifndef NO_LOGS debug(9, "Appending request body: " + std::to_string(len) + " bytes. " \ "Limit set to: " + std::to_string(this->m_rules->requestBodyLimit)); #endif if (this->m_rules->requestBodyLimit > 0 && this->m_rules->requestBodyLimit < len + current_size) { store_variable("INBOUND_DATA_ERROR", "1"); #ifndef NO_LOGS debug(5, "Request body is bigger than the maximum expected."); #endif if (this->m_rules->requestBodyLimitAction == Rules::BodyLimitAction::ProcessPartialBodyLimitAction) { size_t spaceLeft = this->m_rules->requestBodyLimit - current_size; this->m_requestBody.write(reinterpret_cast(buf), spaceLeft); #ifndef NO_LOGS debug(5, "Request body limit is marked to process partial"); #endif return false; } else { if (this->m_rules->requestBodyLimitAction == Rules::BodyLimitAction::RejectBodyLimitAction) { #ifndef NO_LOGS debug(5, "Request body limit is marked to reject the " \ "request"); #endif Action *a = new actions::Deny("deny"); a->temporaryAction = true; actions.push_back(a); } return true; } } this->m_requestBody.write(reinterpret_cast(buf), len); return true; } /** * @name processResponseHeaders * @brief Perform the analysis on the response readers. * * This method perform the analysis on the response headers, notice however * that the headers should be added prior to the execution of this function. * * @note Remember to check for a possible intervention. * * @returns If the operation was successful or not. * @retval true Operation was successful. * @retval false Operation failed. * */ int Assay::processResponseHeaders() { #ifndef NO_LOGS debug(4, "Starting phase RESPONSE_HEADERS. (SecRules 3)"); #endif if (m_rules->secRuleEngine == Rules::DisabledRuleEngine) { #ifndef NO_LOGS debug(4, "Rule engine disabled, returning..."); #endif return true; } this->m_rules->evaluate(ModSecurity::ResponseHeadersPhase, this); return true; } /** * @name addResponseHeader * @brief Adds a response header * * With this method it is possible to feed ModSecurity with a response * header. * * @note This method expects a NULL terminated string, for both: key and * value. * * @param key header name. * @param value header value. * * @returns If the operation was successful or not. * @retval true Operation was successful. * @retval false Operation failed. * */ int Assay::addResponseHeader(const std::string& key, const std::string& value) { m_responseHeadersNames->assign(*m_responseHeadersNames + " " + key); this->store_variable("RESPONSE_HEADERS:" + key, value); if (tolower(key) == "content-type") { this->store_variable("RESPONSE_CONTENT_TYPE", value); } return 1; } /** * @name addResponseHeader * @brief Adds a response header * * With this method it is possible to feed ModSecurity with a response * header. * * @note This method expects a NULL terminated string, for both: key and * value. * * @param key header name. * @param value header value. * * @returns If the operation was successful or not. * @retval true Operation was successful. * @retval false Operation failed. * */ int Assay::addResponseHeader(const unsigned char *key, const unsigned char *value) { return this->addResponseHeader(key, strlen(reinterpret_cast(key)), value, strlen(reinterpret_cast(value))); } /** * @name msc_add_n_response_header * @brief Adds a response header * * Do not expect a NULL terminated string, instead it expect the string and the * string size, for the value and key. * * @param key header name. * @param key_n header name size. * @param value header value. * @param value_n header value size. * * @returns If the operation was successful or not. * @retval true Operation was successful. * @retval false Operation failed. * */ int Assay::addResponseHeader(const unsigned char *key, size_t key_n, const unsigned char *value, size_t value_n) { std::string keys; std::string values; keys.assign(reinterpret_cast(key), key_n); values.assign(reinterpret_cast(value), value_n); return this->addResponseHeader(keys, values); } /** * @name processResponseBody * @brief Perform the request body (if any) * * This method perform the analysis on the request body. It is optional to * call that method. If this API consumer already know that there isn't a * body for inspect it is recommended to skip this step. * * @note It is necessary to "append" the request body prior to the execution * of this method. * @note Remember to check for a possible intervention. * * @returns If the operation was successful or not. * @retval true Operation was successful. * @retval false Operation failed. * */ int Assay::processResponseBody() { #ifndef NO_LOGS debug(4, "Starting phase RESPONSE_BODY. (SecRules 4)"); #endif if (m_rules->secRuleEngine == Rules::DisabledRuleEngine) { #ifndef NO_LOGS debug(4, "Rule engine disabled, returning..."); #endif return true; } if (resolve_variable_first("OUTBOUND_DATA_ERROR") == NULL) { store_variable("OUTBOUND_DATA_ERROR", "0"); } store_variable("RESPONSE_BODY", m_responseBody.str()); store_variable("RESPONSE_CONTENT_LENGTH", std::to_string(m_responseBody.str().size())); this->m_rules->evaluate(ModSecurity::ResponseBodyPhase, this); return true; } /** * @name appendResponseBody * @brief Adds reponse body to be inspected. * * With this method it is possible to feed ModSecurity with data for * inspection regarding the response body. ModSecurity can also update the * contents of the response body, this is not quite ready yet on this version * of the API. * * @note If the content is updated, the client cannot receive the content * length header filled, at least not with the old values. Otherwise * unexpected behavior may happens. * * @returns If the operation was successful or not. * @retval true Operation was successful. * @retval false Operation failed, process partial demanded. * */ int Assay::appendResponseBody(const unsigned char *buf, size_t len) { int current_size = this->m_responseBody.tellp(); #ifndef NO_LOGS debug(9, "Appending response body: " + std::to_string(len + current_size) + " bytes. Limit set to: " + std::to_string(this->m_rules->responseBodyLimit)); #endif if (this->m_rules->responseBodyLimit > 0 && this->m_rules->responseBodyLimit < len + current_size) { store_variable("OUTBOUND_DATA_ERROR", "1"); #ifndef NO_LOGS debug(5, "Response body is bigger than the maximum expected."); #endif if (this->m_rules->responseBodyLimitAction == Rules::BodyLimitAction::ProcessPartialBodyLimitAction) { size_t spaceLeft = this->m_rules->responseBodyLimit - current_size; this->m_responseBody.write(reinterpret_cast(buf), spaceLeft); #ifndef NO_LOGS debug(5, "Response body limit is marked to process partial"); #endif return false; } else { if (this->m_rules->responseBodyLimitAction == Rules::BodyLimitAction::RejectBodyLimitAction) { #ifndef NO_LOGS debug(5, "Response body limit is marked to reject the " \ "request"); #endif Action *a = new actions::Deny("deny"); a->temporaryAction = true; actions.push_back(a); } return true; } } this->m_responseBody.write(reinterpret_cast(buf), len); return true; } /** * @name getResponseBody * @brief Retrieve a buffer with the updated response body. * * This method is needed to be called whenever ModSecurity update the * contents of the response body, otherwise there is no need to call this * method. * * @return It returns a buffer (const char *) * @retval >0 body was update and available. * @retval NULL Nothing was updated. * */ const char *Assay::getResponseBody() { // int there_is_update = this->rules->loadResponseBodyFromJS(this); return this->m_responseBody.str().c_str(); } /** * @name getResponseBodyLenth * @brief Retrieve the length of the updated response body. * * This method returns the size of the update response body buffer, notice * however, that most likely there isn't an update. Thus, this method will * return 0. * * * @return Size of the update response body. * @retval ==0 there is no update. * @retval >0 the size of the updated buffer. * */ int Assay::getResponseBodyLenth() { int size = 0; #if 0 int there_is_update = this->rules->loadResponseBodyFromJS(this); if (there_is_update == -1) { return -1; } #endif this->m_responseBody.seekp(0, std::ios::end); size = this->m_responseBody.tellp(); return size; } /** * @name processLogging * @brief Logging all information relative to this assay. * * At this point there is not need to hold the connection, the response can be * delivered prior to the execution of this method. * * @returns If the operation was successful or not. * @retval true Operation was successful. * @retval false Operation failed. * */ int Assay::processLogging(int returned_code) { #ifndef NO_LOGS debug(4, "Starting phase LOGGING. (SecRules 5)"); #endif if (m_rules->secRuleEngine == Rules::DisabledRuleEngine) { #ifndef NO_LOGS debug(4, "Rule engine disabled, returning..."); #endif return true; } this->httpCodeReturned = returned_code; this->m_rules->evaluate(ModSecurity::LoggingPhase, this); /* If relevant, save this assay information at the audit_logs */ if (m_rules != NULL && m_rules->audit_log != NULL) { #ifndef NO_LOGS debug(8, "Checking if this request is suitable to be saved as an audit log."); #endif int parts = -1; if (this->auditLogModifier.size() > 0) { #ifndef NO_LOGS debug(4, "There was an audit log modifier for this transaction."); #endif std::list>::iterator it; parts = this->m_rules->audit_log->m_parts; for (it = auditLogModifier.begin(); it != auditLogModifier.end(); ++it) { std::pair p = *it; if (p.first == 0) { // Add parts = this->m_rules->audit_log->addParts(parts, p.second); } else { // Remove parts = this->m_rules->audit_log->removeParts(parts, p.second); } } } #ifndef NO_LOGS if (save_in_auditlog) { debug(8, "This request was marked to be saved via auditlog action."); } bool saved = this->m_rules->audit_log->saveIfRelevant(this, parts); if (saved) { debug(8, "Request was relevant to be saved."); } #endif } return true; } /** * @name cleanup * @brief Removes all the resources allocated by a given Assay. * * It is mandatory to call this function after every request being finished, * otherwise it may end up in a huge memory leak. * * @returns If the operation was successful or not. * @retval true Operation was successful. * @retval false Operation failed. * */ void Assay::cleanup() { delete this; } /** * @name intervention * @brief Check if ModSecurity has anything to ask to the server. * * Intervention can generate a log event and/or perform a disruptive action. * * @param Pointer ModSecurityIntervention structure * @retval true A intervention should be made. * @retval false Nothing to be done. * */ bool Assay::intervention(ModSecurityIntervention *it) { it->status = 200; it->url = NULL; it->disruptive = false; if (actions.size() > 0) { for (Action *a : actions) { if (a->action_kind == Action::Kind::RunTimeOnlyIfMatchKind) { a->fill_intervention(it); } if (a->temporaryAction) { delete a; } } actions.clear(); } return it->disruptive; } std::string Assay::toOldAuditLogFormatIndex(const std::string &filename, double size, const std::string &md5) { std::stringstream ss; struct tm timeinfo; char tstr[300]; memset(tstr, '\0', 300); localtime_r(&this->timeStamp, &timeinfo); strftime(tstr, 299, "[%d/%b/%Y:%H:%M:%S %z]", &timeinfo); ss << dash_if_empty( *this->resolve_variable_first("REQUEST_HEADERS:Host")) << " "; ss << dash_if_empty(this->m_clientIpAddress) << " "; /** TODO: Check variable */ ss << dash_if_empty(*this->resolve_variable_first("REMOTE_USER")) << " "; /** TODO: Check variable */ ss << dash_if_empty(*this->resolve_variable_first("LOCAL_USER")) << " "; ss << tstr << " "; ss << "\""; ss << this->m_protocol << " "; ss << this->m_uri << " "; ss << "HTTP/" << m_httpVersion; ss << "\" "; ss << this->httpCodeReturned << " "; ss << this->m_responseBody.tellp(); /** TODO: Check variable */ ss << dash_if_empty(*this->resolve_variable_first("REFERER")) << " "; ss << "\""; ss << dash_if_empty( *this->resolve_variable_first("REQUEST_HEADERS:User-Agent")); ss << "\" "; ss << this->id << " "; /** TODO: Check variable */ ss << dash_if_empty(*this->resolve_variable_first("REFERER")) << " "; ss << filename << " "; ss << "0" << " "; ss << std::to_string(size) << " "; ss << "md5:" << md5 << std::endl; return ss.str(); } std::string Assay::toOldAuditLogFormat(int parts, const std::string &trailer) { std::stringstream audit_log; struct tm timeinfo; char tstr[300]; memset(tstr, '\0', 300); localtime_r(&this->timeStamp, &timeinfo); audit_log << "--" << trailer << "-" << "A--" << std::endl; strftime(tstr, 299, "[%d/%b/%Y:%H:%M:%S %z]", &timeinfo); audit_log << tstr; audit_log << " " << this->id.c_str(); audit_log << " " << this->m_clientIpAddress; audit_log << " " << this->m_clientPort; audit_log << " " << this->m_serverIpAddress; audit_log << " " << this->m_serverPort; audit_log << std::endl; if (parts & AuditLog::BAuditLogPart) { audit_log << "--" << trailer << "-" << "B--" << std::endl; audit_log << this->m_protocol << " " << this->m_uri << " " << "HTTP/"; audit_log << this->m_httpVersion << std::endl; for (auto h : this->m_variables_strings) { std::string filter = "REQUEST_HEADERS:"; std::string a = h.first; std::string b = h.second; if (a.compare(0, filter.length(), filter) == 0) { if (a.length() > filter.length()) { audit_log << a.c_str() + filter.length() << ": "; audit_log << b.c_str() << std::endl; } } } } if (parts & AuditLog::CAuditLogPart) { audit_log << "--" << trailer << "-" << "C--" << std::endl; /** TODO: write audit_log C part. */ } if (parts & AuditLog::DAuditLogPart) { audit_log << "--" << trailer << "-" << "D--" << std::endl; /** TODO: write audit_log D part. */ } if (parts & AuditLog::EAuditLogPart) { audit_log << "--" << trailer << "-" << "E--" << std::endl; /** TODO: write audit_log E part. */ } if (parts & AuditLog::FAuditLogPart) { audit_log << "--" << trailer << "-" << "F--" << std::endl; for (auto h : this->m_variables_strings) { std::string filter = "RESPONSE_HEADERS:"; std::string a = h.first; std::string b = h.second; if (a.compare(0, filter.length(), filter) == 0) { if (a.length() > filter.length()) { audit_log << a.c_str() + filter.length() << ": "; audit_log << b.c_str() << std::endl; } } } } if (parts & AuditLog::GAuditLogPart) { audit_log << "--" << trailer << "-" << "G--" << std::endl; /** TODO: write audit_log G part. */ } if (parts & AuditLog::HAuditLogPart) { audit_log << "--" << trailer << "-" << "H--" << std::endl; /** TODO: write audit_log H part. */ } if (parts & AuditLog::IAuditLogPart) { audit_log << "--" << trailer << "-" << "I--" << std::endl; /** TODO: write audit_log I part. */ } if (parts & AuditLog::JAuditLogPart) { audit_log << "--" << trailer << "-" << "J--" << std::endl; /** TODO: write audit_log J part. */ } if (parts & AuditLog::KAuditLogPart) { audit_log << "--" << trailer << "-" << "K--" << std::endl; /** TODO: write audit_log K part. */ } audit_log << "--" << trailer << "-" << "Z--" << std::endl << std::endl; return audit_log.str(); } std::string Assay::to_json(int parts) { const unsigned char *buf; size_t len; yajl_gen g = NULL; std::string ts = ascTime(&timeStamp).c_str(); std::string uniqueId = UniqueId::uniqueId(); parts = 0; g = yajl_gen_alloc(NULL); if (g == NULL) { return ""; } yajl_gen_config(g, yajl_gen_beautify, 1); /* main */ yajl_gen_map_open(g); /* trasaction */ yajl_gen_string(g, reinterpret_cast("transaction"), strlen("transaction")); yajl_gen_map_open(g); /* Part: A (header mandatory) */ LOGFY_ADD("client_ip", this->m_clientIpAddress); LOGFY_ADD("time_stamp", ts.c_str()); LOGFY_ADD("server_id", uniqueId.c_str()); LOGFY_ADD_NUM("client_port", m_clientPort); LOGFY_ADD("host_ip", m_serverIpAddress); LOGFY_ADD_NUM("host_port", m_serverPort); LOGFY_ADD("id", this->id.c_str()); /* request */ yajl_gen_string(g, reinterpret_cast("request"), strlen("request")); yajl_gen_map_open(g); LOGFY_ADD("protocol", m_protocol); LOGFY_ADD_INT("http_version", m_httpVersion); LOGFY_ADD("uri", this->m_uri); if (parts & AuditLog::CAuditLogPart) { LOGFY_ADD("body", this->m_requestBody.str().c_str()); } /* request headers */ if (parts & AuditLog::BAuditLogPart) { yajl_gen_string(g, reinterpret_cast("headers"), strlen("headers")); yajl_gen_map_open(g); for (auto h : this->m_variables_strings) { std::string filter = "REQUEST_HEADERS:"; std::string a = h.first; std::string b = h.second; if (a.compare(0, filter.length(), filter) == 0) { if (a.length() > filter.length()) { LOGFY_ADD(a.c_str() + filter.length(), b.c_str()); } } } /* end: request headers */ yajl_gen_map_close(g); } /* end: request */ yajl_gen_map_close(g); /* response */ yajl_gen_string(g, reinterpret_cast("response"), strlen("response")); yajl_gen_map_open(g); if (parts & AuditLog::GAuditLogPart) { LOGFY_ADD("body", this->m_responseBody.str().c_str()); } LOGFY_ADD_NUM("http_code", httpCodeReturned); /* response headers */ if (parts & AuditLog::FAuditLogPart) { yajl_gen_string(g, reinterpret_cast("headers"), strlen("headers")); yajl_gen_map_open(g); for (auto h : this->m_variables_strings) { std::string filter = "RESPONSE_HEADERS:"; std::string a = h.first; std::string b = h.second; if (a.compare(0, filter.length(), filter) == 0) { if (a.length() > filter.length()) { LOGFY_ADD(a.c_str() + filter.length(), b.c_str()); } } } /* end: response headers */ yajl_gen_map_close(g); } /* end: response */ yajl_gen_map_close(g); /* producer */ if (parts & AuditLog::HAuditLogPart) { yajl_gen_string(g, reinterpret_cast("producer"), strlen("producer")); yajl_gen_map_open(g); /* producer > libmodsecurity */ LOGFY_ADD("modsecurity", ModSecurity::whoAmI().c_str()); /* producer > connector */ LOGFY_ADD("connector", m_ms->getConnectorInformation().c_str()); /* producer > engine state */ LOGFY_ADD("secrules_engine", Rules::ruleEngineStateString(m_rules->secRuleEngine)); /* producer > components */ yajl_gen_string(g, reinterpret_cast("components"), strlen("components")); yajl_gen_array_open(g); for (auto a : m_rules->components) { yajl_gen_string(g, reinterpret_cast (a.c_str()), a.length()); } yajl_gen_array_close(g); /* end: producer */ yajl_gen_map_close(g); } /* end: transaction */ yajl_gen_map_close(g); /* end: main */ yajl_gen_map_close(g); yajl_gen_get_buf(g, &buf, &len); std::string log(reinterpret_cast(buf), len); yajl_gen_free(g); return log; } void Assay::store_variable(std::string key, std::string value) { this->m_variables_strings.emplace(key, value); } bool Assay::update_variable_first(std::string var, const std::string &value) { auto range = m_variables_strings.equal_range(var); for (auto it = range.first; it != range.second; ++it) { it->second = value; return true; } return false; } void Assay::delete_variable(std::string key) { this->m_variables_strings.erase(key); } void Assay::resolve_variable(const std::string& var, std::list *l) { m_variables_strings.resolveVariable(var, l); size_t ac = var.find(":"); if (ac != std::string::npos) { /* It may be a collection */ for (auto &a : collections) { a.second->resolveVariable(var, l); } } } std::list * Assay::resolve_variable(const std::string& var) { std::list *l = new std::list(); resolve_variable(var, l); return l; } void Assay::serverLog(const std::string& msg) { m_ms->serverLog(m_logCbData, msg); } std::string* Assay::resolve_variable_first(const std::string& var) { auto range = m_variables_strings.equal_range(var); for (auto it = range.first; it != range.second; ++it) { return &it->second; } for (auto &a : collections) { auto range = a.second->equal_range(var); for (auto it = range.first; it != range.second; ++it) { return &it->second; } } return NULL; } std::string* Assay::resolve_variable_first(const std::string& collectionName, const std::string& var) { for (auto &a : collections) { if (tolower(a.first) == tolower(collectionName)) { auto range = a.second->equal_range(toupper(collectionName) + ":" + var); for (auto it = range.first; it != range.second; ++it) { return &it->second; } } } return NULL; } void Assay::setCollection(const std::string& collectionName, const std::string& variableName, const std::string& targetValue) { try { ModSecurityStringVariables *collection; collection = collections.at(collectionName); collection->storeOrUpdateVariable(collectionName + ":" + variableName, targetValue); } catch (...) { #ifndef NO_LOGS debug(9, "don't know any collection named: " + collectionName + ". it was created?"); #endif return; } } /** * @name msc_new_assay * @brief Create a new assay for a given configuration and ModSecurity core. * * The assay is the unit that will be used the inspect every request. It holds * all the information for a given request. * * @note Remember to cleanup the assay when the transaction is complete. * * @param ms ModSecurity core pointer. * @param rules Rules pointer. * * @return Pointer to Assay structure * @retval >0 Assay structure was initialized correctly * @retval NULL Assay cannot be initialized, either by problems with the rules, * problems with the ModSecurity core or missing memory to * allocate the resources needed by the assay. * */ extern "C" Assay *msc_new_assay(ModSecurity *ms, Rules *rules, void *logCbData) { return new Assay(ms, rules, logCbData); } /** * @name msc_process_connection * @brief Perform the analysis on the connection. * * This function should be called at very beginning of a request process, it is * expected to be executed prior to the virtual host resolution, when the * connection arrives on the server. * * @note Remember to check for a possible intervention. * * @param assay ModSecurity assay. * @param client Client's IP address in text format. * @param cPort Client's port * @param server Server's IP address in text format. * @param sPort Server's port * * @returns If the operation was successful or not. * @retval 1 Operation was successful. * @retval 0 Operation failed. * */ extern "C" int msc_process_connection(Assay *assay, const char *client, int cPort, const char *server, int sPort) { return assay->processConnection(client, cPort, server, sPort); } /** * @name msc_process_uri * @brief Perform the analysis on the URI and all the query string variables. * * This function should be called at very beginning of a request process, it is * expected to be executed prior to the virtual host resolution, when the * connection arrives on the server. * * @note There is no direct connection between this function and any phase of * the SecLanguage's phases. It is something that may occur between the * SecLanguage phase 1 and 2. * @note Remember to check for a possible intervention. * * @param assay ModSecurity assay. * @param uri Uri. * @param protocol Protocol (GET, POST, PUT). * @param http_version Http version (1.0, 1.2, 2.0). * * @returns If the operation was successful or not. * @retval 1 Operation was successful. * @retval 0 Operation failed. * */ extern "C" int msc_process_uri(Assay *assay, const char *uri, const char *protocol, const char *http_version) { return assay->processURI(uri, protocol, http_version); } /** * @name msc_process_request_headers * @brief Perform the analysis on the request readers. * * This function perform the analysis on the request headers, notice however * that the headers should be added prior to the execution of this function. * * @note Remember to check for a possible intervention. * * @param assay ModSecurity assay. * * @returns If the operation was successful or not. * @retval 1 Operation was successful. * @retval 0 Operation failed. * */ extern "C" int msc_process_request_headers(Assay *assay) { return assay->processRequestHeaders(); } /** * @name msc_process_request_body * @brief Perform the request body (if any) * * This function perform the analysis on the request body. It is optional to * call that function. If this API consumer already know that there isn't a * body for inspect it is recommended to skip this step. * * @note It is necessary to "append" the request body prior to the execution * of this function. * @note Remember to check for a possible intervention. * * @param assay ModSecurity assay. * * @returns If the operation was successful or not. * @retval 1 Operation was successful. * @retval 0 Operation failed. * */ extern "C" int msc_process_request_body(Assay *assay) { return assay->processRequestBody(); } /** * @name msc_append_request_body * @brief Adds request body to be inspected. * * With this function it is possible to feed ModSecurity with data for * inspection regarding the request body. There are two possibilities here: * * 1 - Adds the buffer in a row; * 2 - Adds it in chunks; * * A third option should be developed which is share your application buffer. * In any case, remember that the utilization of this function may reduce your * server throughput, as this buffer creations is computationally expensive. * * @note While feeding ModSecurity remember to keep checking if there is an * intervention, Sec Language has the capability to set the maximum * inspection size which may be reached, and the decision on what to do * in this case is upon the rules. * * @param assay ModSecurity assay. * * @returns If the operation was successful or not. * @retval 1 Operation was successful. * @retval 0 Operation failed. * */ extern "C" int msc_append_request_body(Assay *assay, const unsigned char *buf, size_t len) { return assay->appendRequestBody(buf, len); } extern "C" int msc_request_body_from_file(Assay *assay, const char *path) { return assay->requestBodyFromFile(path); } /** * @name msc_process_response_headers * @brief Perform the analysis on the response readers. * * This function perform the analysis on the response headers, notice however * that the headers should be added prior to the execution of this function. * * @note Remember to check for a possible intervention. * * @param assay ModSecurity assay. * * @returns If the operation was successful or not. * @retval 1 Operation was successful. * @retval 0 Operation failed. * */ extern "C" int msc_process_response_headers(Assay *assay) { return assay->processResponseHeaders(); } /** * @name msc_process_response_body * @brief Perform the request body (if any) * * This function perform the analysis on the request body. It is optional to * call that function. If this API consumer already know that there isn't a * body for inspect it is recommended to skip this step. * * @note It is necessary to "append" the request body prior to the execution * of this function. * @note Remember to check for a possible intervention. * * @param assay ModSecurity assay. * * @returns If the operation was successful or not. * @retval 1 Operation was successful. * @retval 0 Operation failed. * */ extern "C" int msc_process_response_body(Assay *assay) { return assay->processResponseBody(); } /** * @name msc_append_response_body * @brief Adds reponse body to be inspected. * * With this function it is possible to feed ModSecurity with data for * inspection regarding the response body. ModSecurity can also update the * contents of the response body, this is not quite ready yet on this version * of the API. * * @note If the content is updated, the client cannot receive the content * length header filled, at least not with the old values. Otherwise * unexpected behavior may happens. * * @param assay ModSecurity assay. * * @returns If the operation was successful or not. * @retval 1 Operation was successful. * @retval 0 Operation failed. * */ extern "C" int msc_append_response_body(Assay *assay, const unsigned char *buf, size_t len) { return assay->appendResponseBody(buf, len); } /** * @name msc_add_request_header * @brief Adds a request header * * With this function it is possible to feed ModSecurity with a request header. * * @note This function expects a NULL terminated string, for both: key and * value. * * @param assay ModSecurity assay. * @param key header name. * @param value header value. * * @returns If the operation was successful or not. * @retval 1 Operation was successful. * @retval 0 Operation failed. * */ extern "C" int msc_add_request_header(Assay *assay, const unsigned char *key, const unsigned char *value) { return assay->addRequestHeader(key, value); } /** * @name msc_add_n_request_header * @brief Adds a request header * * Same as msc_add_request_header, do not expect a NULL terminated string, * instead it expect the string and the string size, for the value and key. * * @param assay ModSecurity assay. * @param key header name. * @param key_len header name size. * @param value header value. * @param val_len header value size. * * @returns If the operation was successful or not. * @retval 1 Operation was successful. * @retval 0 Operation failed. * */ extern "C" int msc_add_n_request_header(Assay *assay, const unsigned char *key, size_t key_len, const unsigned char *value, size_t value_len) { return assay->addRequestHeader(key, key_len, value, value_len); } /** * @name msc_add_response_header * @brief Adds a response header * * With this function it is possible to feed ModSecurity with a response * header. * * @note This function expects a NULL terminated string, for both: key and * value. * * @param assay ModSecurity assay. * @param key header name. * @param value header value. * * @returns If the operation was successful or not. * @retval 1 Operation was successful. * @retval 0 Operation failed. * */ extern "C" int msc_add_response_header(Assay *assay, const unsigned char *key, const unsigned char *value) { return assay->addResponseHeader(key, value); } /** * @name msc_add_n_response_header * @brief Adds a response header * * Same as msc_add_response_header, do not expect a NULL terminated string, * instead it expect the string and the string size, for the value and key. * * @param assay ModSecurity assay. * @param key header name. * @param key_len header name size. * @param value header value. * @param val_len header value size. * * @returns If the operation was successful or not. * @retval 1 Operation was successful. * @retval 0 Operation failed. * */ extern "C" int msc_add_n_response_header(Assay *assay, const unsigned char *key, size_t key_len, const unsigned char *value, size_t value_len) { return assay->addResponseHeader(key, key_len, value, value_len); } /** * @name msc_assay_cleanup * @brief Removes all the resources allocated by a given Assay. * * It is mandatory to call this function after every request being finished, * otherwise it may end up in a huge memory leak. * * @param assay ModSecurity assay. * * @returns If the operation was successful or not. * @retval 1 Operation was successful. * @retval 0 Operation failed. * */ extern "C" void msc_assay_cleanup(Assay *assay) { assay->cleanup(); } /** * @name msc_intervention * @brief Check if ModSecurity has anything to ask to the server. * * Intervention can generate a log event and/or perform a disruptive action. * * @param assay ModSecurity assay. * * @return Pointer to ModSecurityIntervention structure * @retval >0 A intervention should be made. * @retval NULL Nothing to be done. * */ extern "C" int msc_intervention(Assay *assay, ModSecurityIntervention *it) { return assay->intervention(it); } /** * @name msc_get_response_body * @brief Retrieve a buffer with the updated response body. * * This function is needed to be called whenever ModSecurity update the * contents of the response body, otherwise there is no need to call this * function. * * @param assay ModSecurity assay. * * @return It returns a buffer (const char *) * @retval >0 body was update and available. * @retval NULL Nothing was updated. * */ extern "C" const char *msc_get_response_body(Assay *assay) { return assay->getResponseBody(); } /** * @name msc_get_response_body_length * @brief Retrieve the length of the updated response body. * * This function returns the size of the update response body buffer, notice * however, that most likely there isn't an update. Thus, this function will * return 0. * * @param assay ModSecurity assay. * * @return Size of the update response body. * @retval ==0 there is no update. * @retval >0 the size of the updated buffer. * */ extern "C" int msc_get_response_body_length(Assay *assay) { return assay->getResponseBodyLenth(); } /** * @name msc_process_logging * @brief Logging all information relative to this assay. * * At this point there is not need to hold the connection, the response can be * delivered prior to the execution of this function. * * @param assay ModSecurity assay. * @param code HTTP code returned to the user. * * @returns If the operation was successful or not. * @retval 1 Operation was successful. * @retval 0 Operation failed. * */ extern "C" int msc_process_logging(Assay *assay, int code) { return assay->processLogging(code); } } // namespace ModSecurity