int perform_interception()

in apache2/mod_security2.c [164:436]


int perform_interception(modsec_rec *msr) {
    msre_actionset *actionset = NULL;
    const char *message = NULL;
    const char *phase_text = "";
    unsigned int pause = 0;
    int status = DECLINED;
    int log_level = 1;

    /* Sanity checks first. */

    if (msr->was_intercepted == 0) {
        msr_log(msr, 1, "Internal Error: Asked to intercept request but was_intercepted is zero");
        return DECLINED;
    }

    if (msr->phase > 4) {
        msr_log(msr, 1, "Internal Error: Asked to intercept request in phase %d.", msr->phase);
        msr->was_intercepted = 0;
        return DECLINED;
    }

    /* OK, we're good to go. */

    actionset = msr->intercept_actionset;
    phase_text = apr_psprintf(msr->mp, " (phase %d)", msr->phase);

    /* By default we log at level 1 but we switch to 4
     * if a nolog action was used or this is not the initial request
     * to hide the message.
     */
    log_level = (actionset->log != 1) ? 4 : 1;

    /* Pause the request first (if configured and the initial request). */
    if (actionset->intercept_pause != NULL) {
        if(strstr(actionset->intercept_pause,"%{") != NULL) {
            msc_string *var = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));

            var->value = (char *)actionset->intercept_pause;
            var->value_len = strlen(actionset->intercept_pause);
            expand_macros(msr, var, NULL, msr->mp);

            pause = atoi(var->value);
            if ((pause == LONG_MAX)||(pause == LONG_MIN)||(pause <= 0))
                pause = 0;

            msr_log(msr, (log_level > 3 ? log_level : log_level + 1), "Pausing transaction for "
                    "%d msec.", pause);
            /* apr_sleep accepts microseconds */
            apr_sleep((apr_interval_time_t)(pause * 1000));
        } else {
            pause = atoi(actionset->intercept_pause);
            if ((pause == LONG_MAX)||(pause == LONG_MIN)||(pause <= 0))
                pause = 0;
            msr_log(msr, (log_level > 3 ? log_level : log_level + 1), "Pausing transaction for "
                    "%d msec.", pause);
            /* apr_sleep accepts microseconds */
            apr_sleep((apr_interval_time_t)(pause * 1000));
        }
    }

    /* Determine how to respond and prepare the log message. */
    switch(actionset->intercept_action) {
        case ACTION_DENY :
            if (actionset->intercept_status != 0) {
                status = actionset->intercept_status;
                message = apr_psprintf(msr->mp, "Access denied with code %d%s.",
                        status, phase_text);
            } else {
                log_level = 1;
                status = HTTP_INTERNAL_SERVER_ERROR;
                message = apr_psprintf(msr->mp, "Access denied with code 500%s "
                        "(Internal Error: Invalid status code requested %d).",
                        phase_text, actionset->intercept_status);
            }
            break;

        case ACTION_PROXY :
#if !(defined(VERSION_IIS)) && !(defined(VERSION_NGINX)) && !(defined(VERSION_STANDALONE))
            if (msr->phase < 3) {
                if (ap_find_linked_module("mod_proxy.c") == NULL) {
                    log_level = 1;
                    status = HTTP_INTERNAL_SERVER_ERROR;
                    message = apr_psprintf(msr->mp, "Access denied with code 500%s "
                        "(Configuration Error: Proxy action to %s requested but mod_proxy not found).",
                        phase_text,
                        log_escape_nq(msr->mp, actionset->intercept_uri));
                } else {
                    msr->r->filename = apr_psprintf(msr->mp, "proxy:%s", actionset->intercept_uri);
                    msr->r->proxyreq = PROXYREQ_REVERSE;
                    msr->r->handler = "proxy-server";
                    status = OK;
                    message = apr_psprintf(msr->mp, "Access denied using proxy to%s %s.",
                        phase_text,
                        log_escape_nq(msr->mp, actionset->intercept_uri));
                }
            } else {
                log_level = 1;
                status = HTTP_INTERNAL_SERVER_ERROR;
                message = apr_psprintf(msr->mp, "Access denied with code 500%s "
                    "(Configuration Error: Proxy action requested but it does not work in output phases).",
                    phase_text);
            }
#else
            log_level = 1;
            status = HTTP_INTERNAL_SERVER_ERROR;
            message = apr_psprintf(msr->mp, "Access denied with code 500%s "
                "(Configuration Error: Proxy action to %s requested but "
                "proxy is only available in Apache version).",
                phase_text,
                log_escape_nq(msr->mp, actionset->intercept_uri));
#endif
            break;

        case ACTION_DROP :
            /* ENH This does not seem to work on Windows. Is there a
             *     better way to drop a connection anyway?
             */
            #if !defined(WIN32) && !defined(VERSION_NGINX)
            {
                extern module core_module;
                apr_socket_t *csd;

#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 2 && AP_SERVER_PATCHLEVEL_NUMBER > 17
                /* For mod_http2 used by HTTP/2 there is a virtual connection so must go through
                 * master to get the main connection or the drop request doesn't seem to do anything.
                 * For HTTP/1.1 master will not be defined so just go through normal connection.
                 * More details here: https://github.com/icing/mod_h2/issues/127
                 */
                if (msr->r->connection->master) {
                    csd = ap_get_module_config(msr->r->connection->master->conn_config, &core_module);
                } else {
                    csd = ap_get_module_config(msr->r->connection->conn_config, &core_module);
                }
#else
		csd = ap_get_module_config(msr->r->connection->conn_config, &core_module);
#endif
                if (csd) {
                    if (apr_socket_close(csd) == APR_SUCCESS) {
                        status = HTTP_FORBIDDEN;
                        message = apr_psprintf(msr->mp, "Access denied with connection close%s.",
                            phase_text);
                    } else {
                        log_level = 1;
                        status = HTTP_INTERNAL_SERVER_ERROR;
                        message = apr_psprintf(msr->mp, "Access denied with code 500%s "
                            "(Error: Connection drop requested but failed to close the "
                            " socket).",
                            phase_text);
                    }
                } else {
                    log_level = 1;
                    status = HTTP_INTERNAL_SERVER_ERROR;
                    message = apr_psprintf(msr->mp, "Access denied with code 500%s "
                        "(Error: Connection drop requested but socket not found.",
                        phase_text);
                }
            }
            #else
            {
                if (modsecDropAction == NULL) {
                    log_level = 1;
                    status = HTTP_INTERNAL_SERVER_ERROR;
                    message = apr_psprintf(msr->mp, "Access denied with code 500%s "
                        "(Error: Connection drop not implemented on this platform.",
                        phase_text);
                } else if (modsecDropAction(msr->r) == 0) {
                    status = HTTP_FORBIDDEN;
                    message = apr_psprintf(msr->mp, "Access denied with connection close%s.",
                        phase_text);
                } else {
                    log_level = 1;
                    status = HTTP_INTERNAL_SERVER_ERROR;
                    message = apr_psprintf(msr->mp, "Access denied with code 500%s "
                        "(Error: Connection drop request failed.",
                        phase_text);
                }
            }
            #endif
            break;

        case ACTION_REDIRECT :
            if(strstr(actionset->intercept_uri,"%{") != NULL) {
                msc_string *var = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));

                var->value = (char *)actionset->intercept_uri;
                var->value_len = strlen(actionset->intercept_uri);
                expand_macros(msr, var, NULL, msr->mp);

                apr_table_setn(msr->r->headers_out, "Location", var->value);
                if ((actionset->intercept_status == 301)||(actionset->intercept_status == 302)
                        ||(actionset->intercept_status == 303)||(actionset->intercept_status == 307))
                {
                    status = actionset->intercept_status;
                } else {
                    status = HTTP_MOVED_TEMPORARILY;
                }
                message = apr_psprintf(msr->mp, "Access denied with redirection to %s using "
                        "status %d%s.",
                        log_escape_nq(msr->mp, var->value), status,
                        phase_text);
            } else {
                apr_table_setn(msr->r->headers_out, "Location", actionset->intercept_uri);
                if ((actionset->intercept_status == 301)||(actionset->intercept_status == 302)
                        ||(actionset->intercept_status == 303)||(actionset->intercept_status == 307))
                {
                    status = actionset->intercept_status;
                } else {
                    status = HTTP_MOVED_TEMPORARILY;
                }
                message = apr_psprintf(msr->mp, "Access denied with redirection to %s using "
                        "status %d%s.",
                        log_escape_nq(msr->mp, actionset->intercept_uri), status,
                        phase_text);
            }
            break;

        case ACTION_ALLOW :
            status = DECLINED;
            message = apr_psprintf(msr->mp, "Access allowed%s.", phase_text);
            msr->was_intercepted = 0;
            msr->allow_scope = ACTION_ALLOW;
            break;

        case ACTION_PAUSE :
            status = DECLINED;
            message = apr_psprintf(msr->mp, "Paused Access%s.", phase_text);
            msr->was_intercepted = 0;
            msr->allow_scope = ACTION_ALLOW;
            break;

        case ACTION_ALLOW_PHASE :
            status = DECLINED;
            message = apr_psprintf(msr->mp, "Access to phase allowed%s.", phase_text);
            msr->was_intercepted = 0;
            msr->allow_scope = ACTION_ALLOW_PHASE;
            break;

        case ACTION_ALLOW_REQUEST :
            status = DECLINED;
            message = apr_psprintf(msr->mp, "Access to request allowed%s.", phase_text);
            msr->was_intercepted = 0;
            msr->allow_scope = ACTION_ALLOW_REQUEST;
            break;

        default :
            log_level = 1;
            status = HTTP_INTERNAL_SERVER_ERROR;
            message = apr_psprintf(msr->mp, "Access denied with code 500%s "
                    "(Internal Error: invalid interception action %d).",
                    phase_text, actionset->intercept_action);
            break;
    }

    /* If the level is not high enough to add an alert message, but "auditlog"
     * is enabled, then still add the message. */
    if ((log_level > 3) && (actionset->auditlog != 0)) {
        *(const char **)apr_array_push(msr->alerts) = msc_alert_message(msr, actionset, NULL, message);
    }

    /* Log the message now. */
    msc_alert(msr, log_level, actionset, message, msr->intercept_message);

    /* However, this will mark the txn relevant again if it is <= 3,
     * which will mess up noauditlog.  We need to compensate for this
     * so that we do not increment twice when auditlog is enabled and
     * prevent incrementing when auditlog is disabled.
     */
    if ((actionset->auditlog == 0) && (log_level <= 3)) {
        msr->is_relevant--;
    }

    return status;
}