/*
 * $Header: /cvsroot/csrc/modsite/site.c,v 1.51 2004/03/17 15:06:10 aleigh
 * Exp $
 */

/*
 * Copyright (c) 1994, 2001 Alex Leigh, All Rights Reserved
 * 
 * THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF ALEX LEIGH The copyright
 * notice above does not evidence any actual or intended publication of such
 * source code.
 * 
 * Disclosure, copying, distribution, or transmission of this code without
 * express permission is prohibited.
 */

#include "../continuity/include/csys.h"
#include "../continuity/mecha/dyn.h"
#include "../continuity/mecha/net.h"
#include "../continuity/include/continuity.h"
#include "../continuity/mecha/lst.h"
#include <http.h>
#include <cmd.h>
#include "site.h"
#include "siteerror.h"

#undef MODSITE_DEBUG

struct siteSacl {
    unsigned int ip;
    unsigned int netmask;
    struct siteSacl *next;
};
typedef struct siteSacl siteTacl;

struct siteSsite {
    const char *siteid;
    size_t siteid_len;
    const char *docroot;
    size_t docroot_len;
    const char *ip_restrict;
    const xmlTtag *config;
    lstTset *redirects;
    hshTvoid_list *external;
    hshTvoid_list *options; /* Used to store <OPTION> values */
};
typedef struct siteSsite siteTsite;

static hshTvoid_list *siteGhost_hash = NULL;    /* hostname -> {site} */
static hshTvoid_list *siteGsite_hash = NULL;    /* siteid -> {site} */
static hshTvoid_list *siteGmime_types = NULL;   /* extension -> mimetype */
static hshTvoid_list *siteGacls = NULL;

static siteTsite *siteFconfig_newsite(const char *siteid);
static void siteFregister_redirect(siteTsite * site, const char *pattern,
                                   const char *target);
static void siteFregister_hostname(siteTsite * site, const char *hostname);
static void siteFconfig_process(void);
static void siteFconfig_site(const xmlTtag * modsite);
static siteTsite *siteFget_by_hostname(const char *hostname);
static siteTsite *siteFget_by_siteid(const char *siteid);
static int siteFdo_error(httpTtrans * t, const char *error,
                         const char *serror);
static int logFreap(int delta);
static void siteFadd_ipacl(const char *name, unsigned int ip,
                           unsigned int netmask);
static void siteFadd_ipacl_strs(const char *name, const char *ip,
                                const char *netmask);
static void siteFset_path(httpTtrans * t, char *path, int path_len);

/* in log.c */
int siteFlog_init(void);
void siteFset_logroot(const char *lr);

int siteFop_show_http_vhosts(netTconn *conn, MECHA_UNUSED_ARGUMENT int argc, MECHA_UNUSED_ARGUMENT char **argv) 
{
  siteTsite *site;
  char *hostname;
  int ret;

  netFconn_writef(conn,"\r\nVirtual Hosts\r\n-------------\r\n");

  if(siteGhost_hash==NULL) {
    netFconn_writef(conn,"No HTTP virtual hosts configured.\r\n");
    return STATUS_OK;
  }

  hshTiterator *it = hshFiterator_init(siteGhost_hash);

  while((ret=hshFiterator_next(it))==1) {
    logFmsg(3,"in loop");

    hostname=hshFiterator_current_key(it);
    site=hshFiterator_current_value(it);

    assert(hostname!=NULL);
    assert(site!=NULL);
   
    netFconn_writef(conn,"%s\t%s\r\n", hostname, site->siteid);
  }

  hshFiterator_free(it);

  return STATUS_OK;
}

static int siteFipacl_auth(const char *name, unsigned int ip)
{
    siteTacl *acl;

    assert(name != NULL);

    acl = (siteTacl *) hshFvoid_find(siteGacls, name);

    if (acl == NULL) {
#ifdef MODSITE_DEBUG
        logFmsg(3, "No ACLs found for %s", name);
#endif
        return 0;
    }
    for (; acl != NULL; acl = acl->next) {
        if ((acl->ip & acl->netmask) == (ip & acl->netmask))
            return 1;
    }

    return 0;
}

static void siteFadd_ipacl_strs(const char *name, const char *ip,
                                const char *netmask)
{
    siteFadd_ipacl(name, utlFstr_to_ip(ip), utlFstr_to_ip(netmask));
}

static void siteFadd_ipacl(const char *name, unsigned int ip,
                           unsigned int netmask)
{
    siteTacl *acl, *new;

    assert(name != NULL);

    new = (siteTacl *) malloc(sizeof(siteTacl));
    bzero(new, sizeof(siteTacl));

    new->ip = ip;
    new->netmask = netmask;

    acl = (siteTacl *) hshFvoid_find(siteGacls, name);
    if (acl == NULL) {
        hshFvoid_add(siteGacls, name, (void *) new);
    } else {
        new->next = acl;
        hshFvoid_replace(siteGacls, name, (void *) new);
    }
}

static siteTsite *siteFget_by_siteid(const char *siteid)
{
    siteTsite *site;

    assert(siteid != NULL);
    assert(siteGsite_hash != NULL);

    site = hshFvoid_find(siteGsite_hash, siteid);

    return site;
}

static siteTsite *siteFget_by_hostname(const char *hostname)
{
    siteTsite *site;

    assert(hostname != NULL);
    assert(siteGhost_hash != NULL);

    site = hshFvoid_find(siteGhost_hash, hostname);

    return site;
}

const xmlTtag *siteFconfig_get_by_siteid (const char *siteid)
{
    siteTsite *site;

    assert (siteid != NULL);

    site = siteFget_by_siteid (siteid);

    if (site == NULL)
    {
        return (NULL);
    }
    else
    {
        return (site->config);
    }
}

/*
 * Create a new site entry, and register it into siteGsite_hash. The site
 * entry is returned.
 */
static siteTsite *siteFconfig_newsite(const char *siteid)
{
    siteTsite *site = (siteTsite *) malloc(sizeof(siteTsite));

    assert(site != NULL);
    assert(siteid != NULL);

    bzero(site, sizeof(siteTsite));
    
    site->siteid = strFdup_size(siteid, &site->siteid_len);

    assert(siteGsite_hash != NULL);

    hshFvoid_add(siteGsite_hash, site->siteid, site);
    
    site->redirects = lstFset_init();
    assert(site->redirects != NULL);

    site->external = hshFvoid_init(NULL);
    assert(site->external != NULL);

    site->options = hshFvoid_init(NULL);
    assert(site->options!=NULL);

    return site;
}

int siteFsite_data_set(const char *siteid, const char *name, void *data)
{
    siteTsite *site;

    site = siteFget_by_siteid(siteid);

    if (site == NULL)
        return -1;

    hshFvoid_add(site->external, name, data);

    return 0;
}

void *siteFsite_data_get(const char *siteid, const char *name)
{
    siteTsite *site;

    site = siteFget_by_siteid(siteid);

    if (site == NULL)
        return NULL;

    return hshFvoid_find(site->external, name);
}

int siteFoption_set(const char *siteid, const char *name, const char *data)
{
    siteTsite *site;

    site = siteFget_by_siteid(siteid);

    if (site == NULL)
        return -1;

    logFmsg(3,"adding %s = %s to %s", name, data, siteid);

    hshFvoid_add(site->options, name, (void *)data);

    return 0;
}

const char *siteFoption_get(const char *siteid, const char *name)
{
    siteTsite *site;

    site = siteFget_by_siteid(siteid);

    if (site == NULL)
        return NULL;

    return hshFvoid_find(site->options, name);
}

static void siteFregister_redirect(siteTsite * site, const char *pattern,
                                   const char *target)
{
    assert(pattern != NULL);
    assert(target != NULL);

    lstFset_add(site->redirects, pattern, target);

#ifdef MODSITE_DEBUG
    logFmsg(3,"Registered redirect %s -> %s", pattern, target);
#endif
}

static void siteFregister_hostname(siteTsite * site, const char *hostname)
{
    assert(site != NULL);
    assert(hostname != NULL);

    hshFvoid_add(siteGhost_hash, hostname, site);
}

static void siteFconfig_ipaccess(const xmlTtag * modsite)
{
    const xmlTtag *acls, *acl, *rule;
    xmlTiterator *it, *cit;
    const char *acl_name, *ip, *netmask;

    acls = xmlFfind_first_tag_children(modsite, "ipacls");

    if (acls == NULL) {
#ifdef MODSITE_DEBUG
        logFmsg(3, "No ACLS.");
#endif
        return;
    }
    
    it = xmlFiterator_init(acls);
    assert(it != NULL);

    while ((acl = xmlFiterator_next_name(it, "ipacl")) != NULL) {
        acl_name = xmlFtag_get_attrib_value(acl, "name");

        if (acl_name == NULL)
            continue;

        cit = xmlFiterate_children(acl);
        assert(cit != NULL);

        while ((rule = xmlFiterator_next_name(cit, "ip_allow")) != NULL) {
            netmask = xmlFtag_get_attrib_value(rule, "netmask");
            ip = xmlFtag_get_value_str(rule);

            if (netmask == NULL || ip == NULL)
                continue;

#ifdef MODSITE_DEBUG
            logFmsg(3, "ip rule for %s: %s/%s", acl_name, ip, netmask);
#endif
            siteFadd_ipacl_strs(acl_name, ip, netmask);
        }

        xmlFiterator_free(cit);
    }

    xmlFiterator_free(it);
}


static void siteFconfig_options(const xmlTtag * modsite, const char *siteid)
{
    const xmlTtag *acls, *acl;
    xmlTiterator *it;

    acls = xmlFfind_first_tag_children(modsite, "options");

    if (acls == NULL) {
        return;
    }
    
    it = xmlFiterator_init(acls);
    assert(it != NULL);

    while ((acl = xmlFiterator_next_name(it, "option")) != NULL) {
        const char *name = xmlFtag_get_attrib_value(acl, "name");
        const char *value = xmlFtag_get_value_str(acl);

        if(name==NULL || value==NULL) {
          continue;
        }

        siteFoption_set(siteid, name, value);
    }

    xmlFiterator_free(it);
}

static void siteFconfig_site(const xmlTtag * modsite)
{
    xmlTiterator *site_it, *host_it;
    const char *siteid;
    const char *value, *value2;
    const xmlTtag *tag;
    const xmlTtag *children;
    siteTsite *site;
    const lstTset *attribs;

    assert(modsite != NULL);

    tag = xmlFfind_first_tag_children(modsite, "global");

    if (tag != NULL) {
        tag = xmlFfind_first_tag(tag, "logroot");
        if (tag != NULL) {
            value = xmlFtag_get_value_str(tag);
            if (value != NULL) {
                siteFset_logroot(value);
            }
        }
    } else {
        logFmsg(1,
                "No logroot defined: no access logs will be created.");
    }

    /* Create the site entry and add it to the hash lists */

    site_it = xmlFiterator_init(modsite);

    while ((tag = xmlFiterator_next_name(site_it, "site")) != NULL) {
        attribs = xmlFtag_get_attribs(tag);

        if (attribs != NULL) {
            siteid = lstFset_get(attribs, "id");

            if (siteid != NULL) {
                site = siteFconfig_newsite(siteid);
                assert(site != NULL);

                children = xmlFtag_get_children(tag);

	        /* Handle the <OPTIONS><OPTION>...</OPTIONS> tags */
                siteFconfig_options(children,siteid);

                if (children != NULL) {
                    site->config = children;

                    /* Set the document path */

                    host_it = xmlFiterator_init(children);

                    tag = xmlFiterator_next_name(host_it, "docroot");

                    if (tag != NULL) {
                        value = xmlFtag_get_value_str(tag);
                        if (value != NULL) {
                            site->docroot =
                                strFcopy_size(value, &site->docroot_len);
                        }
                    }
                    xmlFiterator_free(host_it);

                    /* Add the hostnames */

                    children =
                        xmlFfind_first_tag_children(site->config,
                                                    "hostnames");
                    if (children != NULL) {
                        host_it = xmlFiterator_init(children);
                        while ((tag =
                                xmlFiterator_next_name(host_it,
                                                       "hostname")) !=
                               NULL) {
                            value = xmlFtag_get_value_str(tag);

                            if (value != NULL) {
                                siteFregister_hostname(site, value);
                            }
                        }
                        xmlFiterator_free(host_it);
                    }
                    /* Add the redirects */

                    children =
                        xmlFfind_first_tag_children(site->config,
                                                    "redirects");
                    if (children != NULL) {
                        host_it = xmlFiterator_init(children);
                        while ((tag =
                                xmlFiterator_next_name(host_it,
                                                       "redirect")) !=
                               NULL) {
                            value = xmlFtag_get_value_str(tag);
                            value2 =
                                xmlFtag_get_attrib_value(tag, "pattern");

                            if (value != NULL && value2 != NULL) {
                                siteFregister_redirect(site, value2,
                                                       value);
                            }
                        }
                        xmlFiterator_free(host_it);
                    }
                    /* Add the IPLK access entries */
                    tag = xmlFfind_first_tag(site->config, "ip_restrict");
                    if (tag != NULL) {
                        value = xmlFtag_get_value_str(tag);
                        if (value != NULL) {
                            site->ip_restrict = value;
                        }
                    }
                }
            }
        }
    }

    xmlFiterator_free(site_it);

    site_it = xmlFiterator_init(modsite);
    assert(site_it != NULL);

    tag = xmlFiterator_next_name(site_it, "mimetypes");

    if (tag != NULL) {
        tag = xmlFtag_get_children(tag);
        if (tag != NULL) {
            host_it = xmlFiterator_init(tag);
            assert(host_it != NULL);

            while ((tag =
                    xmlFiterator_next_name(host_it, "mimetype")) != NULL) {
                value = xmlFtag_get_value_str(tag);
                attribs = xmlFtag_get_attribs(tag);
                if (attribs != NULL) {
                    const char *foo = lstFset_get(attribs, "ext");
                    if (foo != NULL && value != NULL) {
                        assert(siteGmime_types != NULL);
                        /*
                         * QUESTION: do we really want to be using the value
                         * we got from xmlFtag_get_value_str() without duping
                         * it?
                         * 
                         * what is the lifetime of the data we get from that
                         * function?
                         */
                        hshFvoid_add(siteGmime_types, foo, (char *) value);
                    }
                }
            }
            xmlFiterator_free(host_it);
        }
    }
    xmlFiterator_free(site_it);

}

static void siteFconfig_process(void)
{
    const xmlTtag *base, *modsite;

    base = conFget_config_basetag();

    assert(base != NULL);

    modsite = xmlFfind_first_tag_children(base, "modsite_config");

    if (modsite != NULL) {
        siteFconfig_site(modsite);
        siteFconfig_ipaccess(modsite);
    }
}

int siteFinit(MECHA_UNUSED_ARGUMENT void *p, const lstTset * opts)
{
    assert(opts != NULL);

    logFmsg(0, "mod/site: WWW Site Management Engine");
    logFmsg(0, "Copyright (c) 2004, Alex Leigh");

    siteGhost_hash = hshFvoid_init(NULL);
    siteGsite_hash = hshFvoid_init(NULL);
    siteGmime_types = hshFvoid_init(NULL);
    siteGacls = hshFvoid_init(NULL);

    siteFconfig_process();

    siteFlog_init();

    cmdTop *root, *op;

    root=cmdFroot();
    assert(root!=NULL);

    op=cmdFmatch("show",root);
    assert(op!=NULL);

    cmdFbind_stub(op,"http","Hypertext Transfer Protocol.");
    op=cmdFmatch("http",op);
    assert(op!=NULL);

    cmdFbind_command(op,"vhosts",siteFop_show_http_vhosts,"Show configured HTTP Virtual Hosts.");

    return STATUS_OK;
}

int siteFredirects(httpTtrans * t, MECHA_UNUSED_ARGUMENT lstTset * opt)
{
    const char *siteid = lstFset_get(t->vars, "siteid");
    siteTsite *site;
    const char *uri;
    int i;

    assert(siteid != NULL);

    site = siteFget_by_siteid(siteid);

    if (site == NULL)
        return STATUS_OK;

    uri = lstFset_get(t->vars, "uri");

    assert(uri != NULL);

#ifdef MODSITE_DEBUG
    logFmsg(3,"Checking redirects...");
#endif

    for (i = 0; i < site->redirects->size; i++) {
        if (site->redirects->fields[i].name != NULL
            && site->redirects->fields[i].value != NULL) {

#ifdef MODSITE_DEBUG
            logFmsg(3, "Checking %s %s",
                    site->redirects->fields[i].name,
                    site->redirects->fields[i].value);
#endif

            if (expFmatch(site->redirects->fields[i].name, uri) == 1)
                return siteFredirect(t, site->redirects->fields[i].value);
        }
    }

#ifdef MODSITE_DEBUG
    logFmsg(3,"Redirect set did not match.");
#endif

    return STATUS_OK;
}

int siteFset_siteid(httpTtrans * t, MECHA_UNUSED_ARGUMENT lstTset * opts)
{
    const char *host;
    siteTsite *site;

    assert(t != NULL);

    host = lstFset_get(t->req_hdrs, "host");

    if (host != NULL) {
        char *lower_host = strFcopy(host);
        strFtolower(lower_host);

        site = siteFget_by_hostname(lower_host);

        free(lower_host);

        if (site == NULL)
            site = siteFget_by_siteid("_default");
    } else {
        site = siteFget_by_siteid("_default");
    }

    if (site == NULL) {
        siteFdo_error(t, "500", "_default website not configured.");
        return STATUS_EXIT;
    }
    if (site->docroot == NULL) {
        siteFdo_error(t, "500", "No document root configured.");
        return STATUS_EXIT;
    }
    lstFset_badd(t->vars, "siteid", 6, site->siteid, site->siteid_len);
    lstFset_badd(t->vars, "docroot", 7, site->docroot, site->docroot_len);

    return STATUS_PROCEED;
}

static int siteFpath_combine(const char *path, int path_len,
                             const char *relative, int relative_len,
                             char *output, int output_len)
{
    if (path_len > output_len) {
        path_len = output_len;
    }
    memcpy(output, path, path_len);

    /* make sure we even have room to do anything else */
    if (path_len == output_len) {
        /*
         * should we return what we could do or return -1 because we couldn't
         * combine them?
         * 
         * -eric@5stops.com - 1.29.2004
         * 
         */
        return (path_len);
    }
    if (output[path_len - 1] != '/') {
        output[path_len++] = '/';
    }
    if (path_len == output_len) {
        return (path_len);
    }
    if (relative[0] == '/') {
        relative++;
        relative_len--;
    }
    if (path_len + relative_len + 1 > output_len) {
        relative_len = output_len - (path_len + 1);
    }
    memcpy(output + path_len, relative, relative_len);

    output[path_len + relative_len] = 0;

    return (path_len + relative_len);
}

static void siteFset_path(httpTtrans * t, char *path,
                          MECHA_UNUSED_ARGUMENT int path_len)
{
    /*
     * "path" should be re-evaluated and possibly changed to something more
     * obvious: "object_location", "entity_path", "entity_location"?
     * 
     * -eric@5stops.com 2.23.2004
     */
    lstFset_bupdate(t->vars, "path", 4, path, path_len);

    /* see if this exists */
    if (stat(path, &t->statb) != -1) {
        t->has_statb = 1;
    } else {
        t->has_statb = 0;
    }
}

static void siteFset_uri(httpTtrans * t, const char *uri,
                         MECHA_UNUSED_ARGUMENT int uri_len)
{
    const lstTfield *docroot = lstFset_get_field(t->vars, "docroot", 7);
    char path[1024];
    int path_len;
    char clean_uri[1024];
    int clean_uri_len;

    clean_uri_len = fleFpath_simplify(uri, clean_uri, sizeof(clean_uri));

    path_len = siteFpath_combine(lstFfield_value(docroot),
                                 lstFfield_value_len(docroot), clean_uri,
                                 clean_uri_len, path, sizeof(path));

    lstFset_bupdate(t->vars, "uri", 3, clean_uri, clean_uri_len);

    /*
     * "path" should be re-evaluated and possibly changed to something more
     * obvious: "object_location", "entity_path", "entity_location"?
     * 
     * -eric@5stops.com 2.23.2004
     */
    siteFset_path(t, path, path_len);
}

int siteFset_path_info(httpTtrans * t, MECHA_UNUSED_ARGUMENT lstTset * opt)
{
    const lstTfield *path = lstFset_get_field(t->vars, "path", 4);
    struct stat sbuf;

    /*
     * a stat() cache would be great here
     */
    if (t->has_statb == 1) {
        /*
         * if our translated path happens to be something on the filesystem,
         * we know we don't have a PATH_INFO to parse.
         */
        return (STATUS_PROCEED);
    }
    {
        char cur_path[1024];
        const lstTfield *uri = lstFset_get_field(t->vars, "uri", 3);
        int uri_len = lstFfield_value_len(uri);
        int path_len = lstFfield_value_len(path);

        char *path_info;
        char *prev_path_info = NULL;
        char *cur_path_end = cur_path + path_len;
        char *cur_uri_start = cur_path_end - uri_len;

        strncpy(cur_path, lstFfield_value(path), sizeof(cur_path));

        while ((path_info = strrchr(cur_path, '/')) != NULL
               && path_info > cur_uri_start) {
            *path_info = 0;

            if (prev_path_info != NULL) {
                *prev_path_info = '/';
            }
            if (stat(cur_path, &sbuf) != -1) {
                if (S_ISREG(sbuf.st_mode)) {
                    int path_info_len = cur_path_end - path_info;
                    char clean_path_info[1024];
                    int clean_path_info_len;

                    char *new_uri = path_info - (uri_len - path_info_len);

                    siteFset_uri(t, new_uri, uri_len - path_info_len);

                    *path_info = '/';

                    clean_path_info_len =
                        fleFpath_simplify(path_info, clean_path_info,
                                          sizeof(clean_path_info));

                    lstFset_badd(t->vars, "path_info", 9, clean_path_info,
                                 clean_path_info_len);

                    /* create the path_translated variable */
                    {
                        char path_translated[1024];
                        int path_translated_len;

                        const lstTfield *docroot =
                            lstFset_get_field(t->vars, "docroot", 7);

                        path_translated_len =
                            siteFpath_combine(lstFfield_value(docroot),
                                              lstFfield_value_len(docroot),
                                              clean_path_info,
                                              clean_path_info_len,
                                              path_translated,
                                              sizeof(path_translated));

                        lstFset_badd(t->vars, "path_translated", 15,
                                     path_translated, path_translated_len);
                    }

#if DEBUG_PATH_INFO
                    logFmsg(CONT_LOG_DEBUG,
                            "site/path_info: PATH_INFO: %s",
                            lstFset_get(t->vars, "path_info"));
                    logFmsg(CONT_LOG_DEBUG, "site/path_info: URI: %s",
                            lstFset_get(t->vars, "uri"));
                    logFmsg(CONT_LOG_DEBUG, "site/path_info: PATH: %s",
                            lstFset_get(t->vars, "path"));
                    logFmsg(CONT_LOG_DEBUG,
                            "site/path_info: PATH_TRANSLATED: %s",
                            lstFset_get(t->vars, "path_translated"));
#endif

                    return (STATUS_PROCEED);
                } else {
                    return (STATUS_PROCEED);
                }
            }
            prev_path_info = path_info;
        }

        return (STATUS_PROCEED);
    }
}

int siteFset_path_translated(httpTtrans * t, lstTset * opt)
{
    return (siteFset_setup_path(t, opt));
}

int siteFset_pathinfo(httpTtrans * t, lstTset * opt)
{
    return (siteFset_setup_path(t, opt));
}

int siteFset_setup_path(httpTtrans * t,
                        MECHA_UNUSED_ARGUMENT lstTset * opt)
{
    const lstTfield *uri;
    const lstTfield *path;
    const char *safe_uri;

    assert(t != NULL);

    uri = lstFset_get_field(t->vars, "uri", 3);

    /*
     * if: - we don't have a uri - our uri is empty - our uri doesn't start
     * with a "/"
     * 
     * we know something is really wrong and we'll just reject the connection.
     * 
     * -eric@5stops.com 2.23.2004
     */
    if (uri == NULL || lstFfield_value_len(uri) == 0 ||
        lstFfield_value(uri)[0] != '/') {
        return STATUS_ERROR;
    }
    /* Keep a copy of the URI because siteFset_uri is going to munge it */
    safe_uri = lstFfield_value(uri);

    siteFset_uri(t, lstFfield_value(uri), lstFfield_value_len(uri));

    path = lstFset_get_field(t->vars, "path", 4);

    /* see if this exists */
    if (t->has_statb == 1) {
        /*
         * Check to see if we've a trailing / on the request, if the stat
         * turned out to be a directory. If not we are going to redirect them
         * to the right address. This is slow and lame, but required so the
         * brower's concept of relative paths will work properly.
         */

        if ((t->statb.st_mode & S_IFMT) == S_IFDIR) {
            const char *uri_ptr;
            int uri_len;

            uri = lstFset_get_field(t->vars, "uri", 3);

            uri_ptr = lstFfield_value(uri);
            uri_len = lstFfield_value_len(uri);

            if (uri_ptr[uri_len - 1] != '/') {
                if (uri_len < 1022) {
                    char lbuf[1024];

                    memcpy(lbuf, uri_ptr, uri_len);
                    lbuf[uri_len] = '/';
                    lbuf[uri_len + 1] = 0;

                    return httpFredirect(t, lbuf);
                }
            }
        }
    }
    return STATUS_PROCEED;
}

#define UNLEN 128
int siteFset_userdir(httpTtrans * t, lstTset * opt)
{
    const char *uri;
    char buf[1024];
    char homedir[512];
    char fp[512];
    char username[UNLEN];
    int i;
    const char *public_dir;
    const char *p;

    assert(t != NULL);

    uri = lstFset_get(t->vars, "uri");

    if (uri == NULL)
        return STATUS_ERROR;

    if (uri[1] != '~')
        return STATUS_PROCEED;

    public_dir = lstFset_get(opt, "public_dir");
    if (public_dir == NULL) {
        public_dir = "public_html";
    }
    p = uri + 2;
    for (i = 0; i < (UNLEN - 1); i++) {
        if (*p == 0 || *p == '/') {
            username[i] = 0;
            break;
        }
        username[i] = *p;
        p++;
    }
    username[i + 1] = 0;

    p = uri + (i + 3);

#ifdef MODSITE_DEBUG
    logFmsg(3, "userdir: Caught USER: %s - new uri %s", username, uri);
#endif

    if (utlFget_homedir(username, homedir, sizeof(homedir)) != -1) {
#ifdef MODSITE_DEBUG
         logFmsg(3, "userdir: %s", homedir);
#endif
    } else {
#ifdef MODSITE_DEBUG
        logFmsg(3, "userdir: No such user.");
#endif
        return STATUS_PROCEED;
    }

    snprintf(fp, 512, "%s/%s/", homedir, public_dir);

#ifdef MODSITE_DEBUG
    logFmsg(3, "new document root is %s", fp);
#endif

    lstFset_update(t->vars, "docroot", fp);

    {
        int path_len =
            snprintf(buf, 512, "%s/%s/%s", homedir, public_dir, p);

#ifdef MODSITE_DEBUG
        logFmsg(3, "new path is ---> %s", buf);
#endif

        siteFset_path(t, buf, path_len);
    }

    if (t->has_statb == 1) {    /* if the file exists */
        /*
         * Check to see if we've a trailing / on the request, if the stat
         * turned out to be a directory. If not we are going to redirect them
         * to the right address. This is slow and lame, but required so the
         * brower's concept of relative paths will work properly.
         */

        if ((t->statb.st_mode & S_IFMT) == S_IFDIR) {
            if (uri[strlen(uri) - 1] != '/') {
                char lbuf[1024];

                snprintf(lbuf, sizeof(lbuf), "%s/", uri);
                return httpFredirect(t, lbuf);
            }
        }
    }
    return STATUS_PROCEED;
}

int siteFfind_index(httpTtrans * t, lstTset * opt)
{
    const char *index_names, *path;
    char *tokptr;
    char names[256];
    char buf[256];
    char *p;
    struct stat statbuf;

    int ret;

    /* Check to see if this request is a directory */
    if ((t->statb.st_mode & S_IFMT) != S_IFDIR)
        return STATUS_PROCEED;

    /* Check to see if we have a list, and an index_names argument */
    if (opt == NULL)
        return STATUS_PROCEED;

    index_names = lstFset_get(opt, "exts");
    if (index_names == NULL)
        return STATUS_PROCEED;

    path = lstFset_get(t->vars, "path");

    strncpy(names, index_names, sizeof(names));

    p = strtok_r(names, ",", &tokptr);

    while (p != NULL) {
        int buf_len = snprintf(buf, sizeof(buf), "%s%s", path, p);

        if (buf_len > 0) {
            ret = stat(buf, &statbuf);
            if (ret == 0) {     /* Found an index file */
                /*
                 * BUGBUG: we're cheating here so we don't have to waste
                 * another stat(), but this should be using siteFset_path()
                 * like everyone else.
                 * 
                 * -eric 3.11.2004
                 */
                memcpy(&t->statb, &statbuf, sizeof(struct stat));
                t->has_statb = 1;

                /*
                 * BUGBUG: this should be using the internal redirector,
                 * right?
                 */
                lstFset_bupdate(t->vars, "path", 4, buf, buf_len);

                return STATUS_PROCEED;
            }
        }
        /* Otherwise */
        p = strtok_r(NULL, ",", &tokptr);
    }

    return STATUS_PROCEED;
}

// TODO: This should really be in mod/http -aleigh
static int siteFdo_error(httpTtrans * t, const char *error,
                         const char *serror)
{
    char hostname[64];
    int ecode;
    const char *err_string, *err_rfc=NULL, *err_txt;

    assert(error!=NULL);
    assert(serror!=NULL);
  
    ecode=atoi(error);

    httpFset_status(t, ecode, NULL);
    lstFset_bupdate(t->res_hdrs, "Content-Type", 12, "text/html", 9);
    httpFstart_response(t);
    httpFsend_response(t);

    gethostname(hostname, 64);

    switch(ecode) {
        case 400:
            err_string="400 - Bad Request";
            err_rfc="10.4.1 400 Bad Request";
            err_txt="The request could not be understood by the server due to malformed syntax. The client " \
                "SHOULD NOT repeat the request without modifications.";
            break;
        case 401:
            err_string="401 - Unauthorized";
            err_rfc="10.4.2 401 Unauthorized";
            err_txt=" The request requires user authentication. The response MUST include a " \
                "WWW-Authenticate header field (section 14.47) containing a challenge applicable " \
                "to the requested resource. The client MAY repeat the request with a suitable " \
                "Authorization header field (section 14.8). If the request already included Authorization " \
                "credentials, then the 401 response indicates that authorization has been refused for " \
                "those credentials. If the 401 response contains the same challenge as the prior response, " \
                "and the user agent has already attempted authentication at least once, then the user " \
                "SHOULD be presented the entity that was given in the response, since that entity " \
                "might include relevant diagnostic information. HTTP access authentication is explained "
                "in \"HTTP Authentication: Basic and Digest Access Authentication\"";
            break;
        case 403:
            err_string="403 - Forbidden";
            err_rfc="10.4.4 403 Forbidden";
            err_txt="The server understood the request, but is refusing to fulfill it. " \
                "Authorization will not help and the request SHOULD NOT be repeated. If the " \
                "request method was not HEAD and the server wishes to make public why the request " \
                "has not been fulfilled, it SHOULD describe the reason for the refusal in the entity. " \
                "If the server does not wish to make this information available to the client, " \
                "the status code 404 (Not Found) can be used instead.";
                break;
        case 404:
            err_rfc="10.4.5 404 Not Found";
            err_txt="The server has not found anything matching the "   \
                "Request-URI. No indication is given of whether the condition is temporary or permanent. The 410 (Gone) status " \
                "code SHOULD be used if the server knows,through some internally configurable mechanism, that an old resource is " \
                "permanently unavailable and has no forwarding address. This status code is commonly used when the server does " \
                "not wish to reveal exactly why the request has been refused, or when no other response is applicable.";
            err_string="404 - Not Found";
            break;
    }

    if(err_rfc==NULL) {
        httpFwritef(t,
                    "<H1>%s error.</H1>%s<P><FONT SIZE=-2>%s<BR>%s</FONT>\n",
                    error, serror, conFget_build(), hostname);
    } else {
        httpFwritef(t,
	           ERROR_FMT, err_string, err_string, err_rfc, err_txt, conFget_build(), hostname);
    }

    return STATUS_EXIT;
}

int siteFfind_mimetype(httpTtrans * t, MECHA_UNUSED_ARGUMENT lstTset * opt)
{
    const char *path, *ext_p, *type;

    path = lstFset_get(t->vars, "path");
    if (path == NULL) {
        logFmsg(1, "find_mimetype: path is missing from t->vars!");
        return STATUS_PROCEED;  /* Not really an error... */
    }
    if ((t->statb.st_mode & S_IFMT) == S_IFDIR) {
        lstFset_bupdate(t->res_hdrs, "Content-Type", 12,
                        "internal/directory", 18);
        return STATUS_EXIT;
    }
    ext_p = strrchr(path, '.');

    if (ext_p == NULL)
        return STATUS_PROCEED;

    ext_p++;

    /* Check to see if the extension is blank */
    if (*ext_p == 0)
        return STATUS_PROCEED;

    type = hshFvoid_find(siteGmime_types, ext_p);

    if (type == NULL) {
        return STATUS_PROCEED;
    }
    lstFset_bupdate(t->res_hdrs, "Content-Type", 12, type, strlen(type));

    return STATUS_EXIT;
}

int siteFset_mimetype(httpTtrans * t, lstTset * opt)
{
    const lstTfield *type;

    type = lstFset_get_field(opt, "type", 4);

    if (type == NULL) {
        logFmsg(2, "force_type: No default mime-type set!");
        return STATUS_ERROR;
    }
    lstFset_bupdate(t->res_hdrs, "Content-Type", 12,
                    lstFfield_value(type), lstFfield_value_len(type));

    return STATUS_PROCEED;
}

/*
 * Send a file. By the time this is called, the current stat structure in the
 * request is expected to point to the file that will be served. This file
 * will 404 rather than serve a directory.
 */

int siteFsend_file(httpTtrans * t, MECHA_UNUSED_ARGUMENT lstTset * opt)
{
    const char *path;
    char modified[40];
    int ret;

    assert(t != NULL);

    tasFstat_counter_add("http_svc", 0, "siteFsend_file", 1);

    httpFmaterial_compare(t, t);

    if ((t->statb.st_mode & S_IFMT) == S_IFDIR) {
        siteFdo_error(t, "404", "Directory found.");
        return STATUS_EXIT;
    }
    if (t->statb.st_mtime == 0) {
        /*
         * invalid modification time, the stat was likely never done. The
         * file is 404.
         */
        siteFdo_error(t, "404", "File not found.");
        return STATUS_EXIT;
    }
    path = lstFset_get(t->vars, "path");

    /* Setup the last-modified time based on the filesystem */
    ret =
        utlFhttp_time_print(t->statb.st_mtime, modified, sizeof(modified));
    lstFset_bupdate(t->res_hdrs, "Last-Modified", 13, modified, ret);

#if WANT_IF_MODIFIED_CHECK
    /*
     * Check to see if we the client passed us a modified. If it did, check
     * to see if we matched, and if we did, return an empty 304 response. The
     * client has it cached.
     */

    {
        char *hdr_modified;

        hdr_modified = lstFset_get(t->req_hdrs, "if-modified-since");

        if (hdr_modified != NULL) {
            if (t->has_statb == 1
                && t->statb.st_mtime ==
                utlFhttp_time_parse(hdr_modified)) {
#ifdef MODSITE_DEBUG
                logFmsg(3, "sendfile: returning 304.");
#endif
                lstFset_nadd(t->res_hdrs, "Content-Length", 0);

                httpFset_status(t, 304, NULL);
                httpFstart_response(t);

                return STATUS_EXIT;
            }
        }
    }
#endif

    /* Set the content length and return the file */
    lstFset_nadd(t->res_hdrs, "Content-Length", t->statb.st_size);

    httpFset_status(t, 200, NULL);
    httpFstart_response(t);
    httpFsend_response(t);

    xfrFmmap(t->conn, path, &t->statb);

    return STATUS_EXIT;
}

int siteFip_restrict(httpTtrans * t, MECHA_UNUSED_ARGUMENT lstTset * opts)
{
    const char *siteid;
    siteTsite *site;

    siteid = lstFset_get(t->vars, "siteid");
    assert(siteid != NULL);

    site = siteFget_by_siteid(siteid);

    assert(site != NULL);

    if (site->ip_restrict != NULL) {
        if (siteFipacl_auth(site->ip_restrict, t->cli_ipv4_addr) == 0) {
            httpFset_status(t, HTTP_FORBIDDEN, NULL);
            httpFstart_response(t);
            httpFsend_response(t);
            httpFwritef(t, "Access Forbidden");
            return STATUS_EXIT;
        }
    }
    return STATUS_PROCEED;
}

int siteFredirect(httpTtrans *t, const char *target) {
  const char *siteid = lstFset_get(t->vars,"siteid");
  
  assert(siteid!=NULL);

  const char *preserve=siteFoption_get(siteid,"preserve_query_on_302");
  if(preserve==NULL) {
    return httpFredirect(t,target);
  }

  const char *query = lstFset_get(t->vars,"query");
  if(query==NULL) {
    return httpFredirect(t,target);
  }

  dynTstring *newurl = dynFinit();

  assert(newurl!=NULL);

  // TODO PERF: This could be done by geting the values and using append rather
  // than the more expensive _print()

  if(strstr(target,"?")==NULL) {
    dynFappend_print(newurl,"%s?%s",target,query);
  } else {
    dynFappend_print(newurl,"%s&%s",target,query);
  }

  int ret=httpFredirect(t,dynFgetstr(newurl));

  dynFfree(newurl);

  return ret;
}

 
