/*
 * mod_allowdev: prohibit files from being served unless they're
 * on a listed device.
 *
 * v0.04
 * 
 * Author: Dean Gaudet <dgaudet@arctic.org>
 *         dirkx@webweaving.org  AllowDev directive
 *
 * This module would be released under the Apache license, but that
 * license can't be used by third parties verbatim... and I'm just too
 * lazy to copy another license in here.  So this will have to do:
 *
 * Copyright (c) 1998 Dean Gaudet, all rights reserved.
 */

/*

Why do you want this?  Let me put it this way:  the symlink protection
options (FollowSymLinks and SymLinksIfOwnerMatch) are lame in many
ways:

- They're slow, they require a component by component stat()
    and/or lstat().

- They're hard to get correct because apache won't readlink() so it
    doesn't rewrite the destination.  This can lead to situations
    where you thought you had protected a filesystem, but you
    really hadn't because a symlink may let the user into it.

- They're overkill.  Frequently all you're trying to do is to protect
    /etc from being served, and frequently /etc is on a partition that
    users' files are not on (if not you've got other problems, see
    a book on unix security).

There's an easier way to do this.  We just tell Apache what *devices*
we are willing to serve files from.  That's what this module does.

*/

/*

Usage: Stick an appropriate "AddModule modules/extra/mod_allowdev.o"
directive at the very bottom of your src/Configuration file, rebuild.

Static mount points:  This probably covers most internet servers.
You list all the mount points that have content you wish to serve
on the net like this:

    AllowDev /mount-point1 /mount-point2 ...

For example, "AllowDev /var" would allow any file on the /var device
to be served.  Note that, for example, "AllowDev /var/foo" where foo
is not a mount point, probably doesn't do what you expect.  This case
too would allow all files on /var to be served.

Dynamic mount points:  This probably covers most intranet servers.

    AllowDevDynamic regex subst

If the file to be served matches regex, then perform subst.  The
resulting path must be on the same device as the file served.  For
example:

    AllowDevDynamic /home/([^/]*)/public_html /home/$1

Says that if a file /home/userid/public_html/foobar is to be served, then
its device must match /home/userid.

*/

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>


#include "httpd.h"
#include "http_config.h"
#include "http_log.h"

module MODULE_VAR_EXPORT allowdev_module;

typedef struct {
    regex_t *regexp;
    char *subst;
} a_dynamic_dev;

typedef struct {
    array_header *static_devs;
    array_header *dynamic_devs;
} a_server_config;

static void *create_server_config(pool *p, server_rec *s)
{
    a_server_config *sec;
    
    sec = ap_palloc(p, sizeof(*sec));
    sec->static_devs = ap_make_array(p, 5, sizeof(dev_t));
    sec->dynamic_devs = ap_make_array(p, 5, sizeof(a_dynamic_dev));
    return sec;
}

static const char *add_dev_slot(cmd_parms *cmd, void *dummy, char *args)
{
    a_server_config *sec;
    struct stat buf;
    dev_t *cur;
    dev_t *end;

    if ((!(*args)) || (strlen(args)==0))	
	return "Must define a file/device to stat";

    if (stat(args, &buf) == -1) {
	ap_log_error(APLOG_MARK, APLOG_WARNING, NULL,
	    "unable to stat %s, files on this device will not be served",
	    args);
	return NULL;
    }

    sec = ap_get_module_config(cmd->server->module_config, &allowdev_module);
    /* though we could return an error; we try to
     * just silently avoid putting the same device
     * in twice.
     */
    cur = (dev_t *)sec->static_devs->elts;
    end = cur + sec->static_devs->nelts;
    while (cur < end) {
	if (*cur == buf.st_dev) {
	    return NULL;
	}
	++cur;
    }

    cur = ap_push_array(sec->static_devs);
    *cur = buf.st_dev;
    return NULL;
}

static const char *add_dynamic(cmd_parms *cmd, void *dummy, char *rx, char *subst)
{
    regex_t *regexp;
    a_dynamic_dev *dyn;
    a_server_config *sec;

    regexp = ap_pregcomp(cmd->pool, rx, REG_EXTENDED);
    if (regexp == NULL) {
	return "regex could not be compiled";
    }
    sec = ap_get_module_config(cmd->server->module_config, &allowdev_module);
    dyn = ap_push_array(sec->dynamic_devs);
    dyn->regexp = regexp;
    dyn->subst = subst;
    return NULL;
}

static command_rec allowdev_cmds[] =
{
    {"AllowDev", add_dev_slot, NULL, RSRC_CONF, ITERATE,
	"A space seperated list of devices or files"}, 
    {"AllowDevDynamic", add_dynamic, NULL, RSRC_CONF, TAKE2,
	"A regexp and an expansion, if uri matches regexp, file must be on device specified by expansion"},
    { NULL }
};
	
/* We run this in the fixup phase because the fixup phase is
 * guaranteed to be run on all requests, and sub requests.
 * We can't use the "check access" phase because that is overridden
 * trivially with the Satisfy directive.
 */
static int check_device(request_rec *r)
{
    a_server_config *sec;
    dev_t *s_cur;
    dev_t *s_end;
    a_dynamic_dev *d_cur;
    a_dynamic_dev *d_end;
    regmatch_t regm[10];

    if (r->finfo.st_mode == 0) {
	return DECLINED;
    }

    sec = ap_get_module_config(r->server->module_config, &allowdev_module);
    s_cur = (dev_t *)sec->static_devs->elts;
    s_end = s_cur + sec->static_devs->nelts;
    while (s_cur < s_end) {
	if (*s_cur == r->finfo.st_dev) {
	    return DECLINED;
	}
	++s_cur;
    }

    d_cur = (a_dynamic_dev *)sec->dynamic_devs->elts;
    d_end = d_cur + sec->dynamic_devs->nelts;
    while (d_cur < d_end) {
	if (!regexec(d_cur->regexp, r->uri,
		d_cur->regexp->re_nsub + 1, regm, 0)) {
	    char *found;
	    struct stat buf;
	    
	    found = ap_pregsub(r->pool, d_cur->subst, r->uri,
			d_cur->regexp->re_nsub + 1, regm);
	    if (stat(found, &buf) == 0
		&& buf.st_dev == r->finfo.st_dev) {
		return DECLINED;
	    }
	}
	++d_cur;
    }

    ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server,
	"mod_allowdev: request to %s is on device 0x%x, forbidden",
	r->uri, r->finfo.st_dev);
    return HTTP_FORBIDDEN;
}

module MODULE_VAR_EXPORT allowdev_module =
{
    STANDARD_MODULE_STUFF,
    NULL,			/* initializer */
    NULL,			/* dir config creater */
    NULL,			/* dir merger --- default is to override */
    create_server_config,	/* server config */
    NULL,			/* merge server config */
    allowdev_cmds,		/* command handlers */
    NULL,			/* handlers */
    NULL,			/* filename translation */
    NULL,			/* check_user_id */
    NULL,			/* check auth */
    NULL,			/* check access */
    NULL,			/* type_checker */
    check_device,		/* fixups */
    NULL,			/* logger */
    NULL,			/* header parser */
    NULL,			/* child_init */
    NULL,			/* child_exit */
    NULL			/* post read-request */
};
