/*
 * mod_restrictrange - the response to the Full-Disclosure "apachekiller" range-request
 *                     pain.
 */
#include "ap_config.h"

#include "apr.h"
#include "apr_strings.h"
#include "httpd.h"
#include "http_config.h"
#include "http_request.h"
#include "http_protocol.h"
#include "http_log.h"

module AP_MODULE_DECLARE_DATA restrictrange_module;

struct restrictrange_conf {
    int numcommas;
    int flags;
};

static int check_range_commas(request_rec *r) {
    struct restrictrange_conf *sconf = (struct restrictrange_conf *)
        ap_get_module_config(r->server->module_config, &restrictrange_module);
    const char *range;
    int i, j;
    int status = DECLINED;
    
    range = r->range;
    if(!range)
        range = apr_table_get(r->headers_in, "Range");

    if(!range)
        return DECLINED;

    for(i = j = 0; range[j]; j++)
        if(range[j] == ',') i++;

    if(i > sconf->numcommas) {
	if(sconf->flags & 0x1) {
            r->range = NULL;
	    apr_table_unset(r->headers_in, "Range");
	}
	else {
	    status = HTTP_BAD_REQUEST;
	}
	if(sconf->flags & 0x2) {
	    ap_log_rerror(NULL, 0, APLOG_WARNING, APR_SUCCESS, r,
	        "Found apache-killer type range request (%d sections)", i+1);
	}
    }

    return status;
}

static void register_hooks(apr_pool_t *p) {
    ap_hook_post_read_request(check_range_commas, NULL, NULL, APR_HOOK_FIRST - 1);
}

static void *restrictrange_create_sconf(apr_pool_t *pool, server_rec *s) {
    struct restrictrange_conf *sconf = NULL;
    sconf = (struct restrictrange_conf *)apr_pcalloc(pool, sizeof(struct restrictrange_conf));

    sconf->numcommas = 4;
    sconf->flags = (0x1 | 0x2);

    return sconf;
}

static char *restrict_commas(cmd_parms *cmd, void *dummy, const char *arg) {
    struct restrictrange_conf *conf = (struct restrictrange_conf *)
        ap_get_module_config(cmd->server->module_config, &restrictrange_module);
    int num = atoi(arg);

    if(!num || num < 0) {
        return "Argument must be a positive integer";
    }

    conf->numcommas = num;
    return NULL;
}

static char *restrict_log(cmd_parms *cmd, void *dummy, int arg) {
    struct restrictrange_conf *conf = (struct restrictrange_conf *)
        ap_get_module_config(cmd->server->module_config, &restrictrange_module);

    conf->flags = ((arg ? 0x2 : 0) | (conf->flags & ~0x2));
    return NULL;
}

static char *restrict_strip(cmd_parms *cmd, void *dummy, int arg) {
    struct restrictrange_conf *conf = (struct restrictrange_conf *)
        ap_get_module_config(cmd->server->module_config, &restrictrange_module);

    conf->flags = ((arg ? 0x1 : 0) | (conf->flags & ~0x1));
    return NULL;
}

static const command_rec restrictrange_cmds[] = {
    AP_INIT_TAKE1("restrictrangecommas", restrict_commas, NULL, RSRC_CONF, "Restrict the number of commas allowed"),
    AP_INIT_FLAG("restrictrangelog", restrict_log, NULL, RSRC_CONF, "Whether to log restricted requests"),
    AP_INIT_FLAG("restrictrangestrip", restrict_strip, NULL, RSRC_CONF, "Whether to just strip the range header (as opposed to returning a 400 Bad Request)"),
    {NULL}
};

module AP_MODULE_DECLARE_DATA restrictrange_module = {
	STANDARD20_MODULE_STUFF,    /* default module structure */
	NULL,                       /* per-dir config creation */
	NULL,                       /* per-dir config merge */
	restrictrange_create_sconf, /* server config creation */
	NULL,                       /* server config merge */
	restrictrange_cmds,         /* command table */
	register_hooks              /* hook registration */
	};

