/*
 * Copyright (c) 1999-2001 Caucho Technology.  All rights reserved.
 *
 * Caucho Technology permits redistribution, modification and use
 * of this file in source and binary form ("the Software") under the
 * Caucho Developer Source License ("the License").  In particular,
 * the following conditions must be met:
 *
 * 1. Each copy or derived work of the Software must preserve the copyright
 *    notice and this notice unmodified.
 *
 * 2. Redistributions of the Software in source or binary form must include 
 *    an unmodified copy of the License, normally in a plain ASCII text
 *
 * 3. The names "Resin" or "Caucho" are trademarks of Caucho Technology and
 *    may not be used to endorse products derived from this software.
 *    "Resin" or "Caucho" may not appear in the names of products derived
 *    from this software.
 *
 * 4. Caucho Technology requests that attribution be given to Resin
 *    in any manner possible.  We suggest using the "Resin Powered"
 *    button or creating a "powered by Resin(tm)" link to
 *    http://www.caucho.com for each page served by Resin.
 *
 * This Software is provided "AS IS," without a warranty of any kind. 
 * ALL EXPRESS OR IMPLIED REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
 * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
 * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED.
 *
 * CAUCHO TECHNOLOGY AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES
 * SUFFERED BY LICENSEE OR ANY THIRD PARTY AS A RESULT OF USING OR
 * DISTRIBUTING SOFTWARE. IN NO EVENT WILL CAUCHO OR ITS LICENSORS BE LIABLE
 * FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
 * INABILITY TO USE SOFTWARE, EVEN IF HE HAS BEEN ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGES.
 *
 * @author Scott Ferguson
 */

#include "httpd.h"
#include "http_config.h"
#include "http_protocol.h"
#include "ap_config.h"
#include "apr_strings.h"
#include <stdlib.h>
#include <errno.h>

#include "cse.h"
#include "version.h"

/*
 * Apache magic module declaration.
 */
module AP_MODULE_DECLARE_DATA caucho_module;

#define BUF_LENGTH 8192
#define DEFAULT_PORT 6802

static char *g_error_page = 0;

void
cse_log(char *fmt, ...)
{
#ifdef DEBUG
  va_list args;
  FILE *file = fopen("/tmp/log", "a+");

  if (file) {  
    va_start(args, fmt);
    vfprintf(file, fmt, args);
    va_end(args);
    fclose(file);
  }
#endif
}

void *
cse_create_lock(config_t *config)
{
  apr_lock_t *lock = 0;

  apr_lock_create(&lock, APR_MUTEX, APR_INTRAPROCESS, "foo", config->web_pool);

  return lock;
}

int
cse_lock(void *vlock)
{
  apr_lock_t *lock = vlock;

  if (lock)
    apr_lock_acquire(lock);
  
  return 1;
}

void
cse_unlock(void *vlock)
{
  apr_lock_t *lock = vlock;

  if (lock)
    apr_lock_release(lock);
}

void
cse_error(config_t *config, char *format, ...)
{
  char buf[BUF_LENGTH];
  va_list args;

  va_start(args, format);
  vsprintf(buf, format, args);
  va_end(args);

  LOG(("%s\n", buf));

  config->error = cse_strdup(config->p, buf);
}

void
cse_set_socket_cleanup(int socket, void *pool)
{
  LOG(("set cleanup %d\n", socket));

  /* XXX:
  if (socket > 0)
    apr_note_cleanups_for_socket(pool, socket);
  */
}

void
cse_kill_socket_cleanup(int socket, void *pool)
{
  /* XXX:
  LOG(("kill cleanup %d\n", socket));

  if (socket > 0)
    apr_kill_cleanups_for_socket(pool, socket);
  */
}

void *
cse_malloc(int size)
{
  return malloc(size);
}

void cse_free(config_t *config, void *data) {}

static void
cse_module_init(struct server_rec *server, apr_pool_t *pool)
{
// XXX: ap_add_version_component(VERSION);
}

static void *
cse_create_dir_config(apr_pool_t *p, char *dummy)
{
  config_t *config = (config_t *) apr_pcalloc(p, sizeof(config_t));
  srun_item_t *srun_list = (srun_item_t *) apr_pcalloc(p, 16 * sizeof(srun_item_t));
  srun_t *srun;

  memset(config, 0, sizeof(config_t));
  memset(srun_list, 0, 16 * sizeof(srun_item_t));

  config->web_pool = p;
  config->p = cse_create_pool(config);
  config->srun_list = srun_list;
  config->srun_capacity = 16;
  config->srun_size = 1;
  
  return (void *) config;
}

/**
 * Retrieves the caucho configuration from Apache
 */
static config_t *
cse_get_module_config(request_rec *r)
{
  config_t *config;

  LOG(("get config %x\n", r->per_dir_config));
  config = (config_t *) ap_get_module_config(r->per_dir_config,
                                             &caucho_module);

  return config;
}

/**
 * Parse the CauchoConfigHost configuration in the apache config file.
 */
static const char *
cse_config_file_command(cmd_parms *cmd, void *pconfig, char *value)
{
  config_t *config = pconfig;
 
  config->path = (char *) ap_server_root_relative(cmd->pool, value);

  cse_init_config(config);

  return 0;
}

/**
 * Gets the session index from the request
 *
 * Cookies have priority over the query
 *
 * @return -1 if no session
 */
static int
get_session_index(config_t *config, request_rec *r)
{
  apr_array_header_t *header = apr_table_elts(r->headers_in);
  apr_table_entry_t *headers = (apr_table_entry_t *) header->elts;
  int i;
  int session;

  for (i = 0; i < header->nelts; ++i) {
    if (! headers[i].key || ! headers[i].val)
      continue;

    if (strcasecmp(headers[i].key, "Cookie"))
      continue;

    session = cse_session_from_string(headers[i].val, "JSESSIONID=");
    if (session >= 0)
      return session;
  }

  return cse_session_from_string(r->uri, config->session_url_prefix);
}

/**
 * Forward to an error page.
 */
static int
handle_include(char *uri, request_rec *r)
{
#ifdef XXX  
  request_rec *subrequest = ap_sub_req_lookup_uri(uri, r);
  int status;

  LOG(("forwarding %s %d\n", uri, subrequest->status));
  
  if (subrequest->status != HTTP_OK)
    return -1;

  /* copy the args from the original request */
  subrequest->path_info = r->path_info;
  subrequest->args = r->args;

  /* Run it. */

  status = ap_run_sub_req(subrequest);

  ap_destroy_sub_req(subrequest);

  return status;
#endif
  return -1;
}

static int
connection_error(config_t *config, srun_t *srun, request_rec *r)
{
  char *error_page = config->error_page;
  
  if (g_error_page)
    error_page = g_error_page;

  if (error_page) {
    int status = handle_include(error_page, r);
    if (status == OK)
      return status;
  }

  r->content_type = "text/html";
  if (error_page) {
    r->status = 302;
    apr_table_set(r->headers_out, "Location", error_page);
  }
  else
    r->status = 503;

  ap_rputs("<html><body bgcolor='white'>", r);
  if (! srun) {
    ap_rprintf(r, "<h1>Can't contact servlet runner</h1>");
  }
  else
    ap_rprintf(r, "<h1>Can't contact servlet runner at %s:%d</h1>", 
               srun->hostname ? srun->hostname : "localhost",
               srun->port);
  ap_rputs("</body></html>", r);
  
  ap_rputs("\n\n\n\n", r);
  ap_rputs("<!--\n", r);
  ap_rputs("  -- Unfortunately, Microsoft has added a clever new\n", r);
  ap_rputs("  -- \"feature\" to Internet Explorer.  If the text in\n", r);
  ap_rputs("  -- an error's message is \"too small\", specifically\n", r);
  ap_rputs("  -- less than 512 bytes, Internet Explorer returns\n", r);
  ap_rputs("  -- its own error message.  Yes, you can turn that\n", r);
  ap_rputs("  -- off, but *surprise* it's pretty tricky to find\n", r);
  ap_rputs("  -- buried as a switch called \"smart error\n", r);
  ap_rputs("  -- messages\"  That means, of course, that many of\n", r);
  ap_rputs("  -- Resin's error messages are censored by default.\n", r);
  ap_rputs("  -- And, of course, you'll be shocked to learn that\n", r);
  ap_rputs("  -- IIS always returns error messages that are long\n", r);
  ap_rputs("  -- enough to make Internet Explorer happy.  The\n", r);
  ap_rputs("  -- workaround is pretty simple: pad the error\n", r);
  ap_rputs("  -- message with a big comment to push it over the\n", r);
  ap_rputs("  -- five hundred and twelve byte minimum.  Of course,\n", r);
  ap_rputs("  -- that's exactly what you're reading right now.\n", r);
  ap_rputs("  -->\n", r);

  /* XXX:
  ap_kill_timeout(r);
  */

  return OK;
}

/**
 * Writes request parameters to srun.
 */
static void
write_env(stream_t *s, request_rec *r)
{
  char buf[4096];
  int ch;
  int i;
  
  conn_rec *c = r->connection;
  const char *host;
  int port;

  cse_write_string(s, CSE_PROTOCOL, r->protocol);
  cse_write_string(s, CSE_METHOD, r->method);
  for (i = 0; (ch = r->uri[i]) && ch != '?' && i + 1 < sizeof(buf); i++) 
    buf[i] = ch;
  buf[i] = 0;
  cse_write_string(s, CSE_URI, buf);

  if (r->args)
    cse_write_string(s, CSE_QUERY_STRING, r->args);

  /* Gets the server name */
  host = ap_get_server_name(r);
  port = r->server->port;

  cse_write_string(s, CSE_SERVER_NAME, host);
  sprintf(buf, "%u", port);
  cse_write_string(s, CSE_SERVER_PORT, buf);

  if (c->remote_host)
    cse_write_string(s, CSE_REMOTE_HOST, c->remote_host);
  else
    cse_write_string(s, CSE_REMOTE_HOST, c->remote_ip);

  cse_write_string(s, CSE_REMOTE_ADDR, c->remote_ip);
  sprintf(buf, "%u", ntohs(c->remote_addr->port));
  cse_write_string(s, CSE_REMOTE_PORT, buf);

  if (r->user)
    cse_write_string(s, CSE_REMOTE_USER, r->user);
  if (r->ap_auth_type)
    cse_write_string(s, CSE_AUTH_TYPE, r->ap_auth_type);

  sprintf(buf, "%d", s->srun->session);
  cse_write_string(s, CSE_SESSION_GROUP, buf);

  /* mod_ssl */
#ifdef EAPI
  {
    static char *vars[] = { "SSL_CLIENT_S_DN",
                            "SSL_CIPHER",
                            "SSL_CIPHER_EXPORT",
                            "SSL_PROTOCOL",
                            "SSL_CIPHER_USEKEYSIZE",
                            "SSL_CIPHER_ALGKEYSIZE",
                            0};
    char *var;
    int i;
    int v;
    
    if ((v = ap_hook_call("ap::mod_ssl::var_lookup", &var, r->pool, r->server,
                          r->connection, r, "SSL_CLIENT_CERT"))) {
      cse_write_string(s, CSE_CLIENT_CERT, var);
    }

    for (i = 0; vars[i]; i++) {
      if ((v = ap_hook_call("ap::mod_ssl::var_lookup", &var,
                            r->pool, r->server, r->connection, r, vars[i]))) {
        cse_write_string(s, CSE_HEADER, vars[i]);
        cse_write_string(s, CSE_VALUE, var);
      }
    }
  }
#endif  
}

/**
 * Writes headers to srun.
 */
static void
write_headers(stream_t *s, request_rec *r)
{
  apr_array_header_t *header = apr_table_elts(r->headers_in);
  apr_table_entry_t *headers = (apr_table_entry_t *) header->elts;
  int i;

  for (i = 0; i < header->nelts; ++i) {
    if (! headers[i].key)
      continue;

    /*
     * Content-type and Content-Length are special cased for a little
     * added efficiency.
     */

    if (! strcasecmp(headers[i].key, "Content-type"))
      cse_write_string(s, CSE_CONTENT_TYPE, headers[i].val);
    else if (! strcasecmp(headers[i].key, "Content-length"))
      cse_write_string(s, CSE_CONTENT_LENGTH, headers[i].val);
    else {
      cse_write_string(s, CSE_HEADER, headers[i].key);
      cse_write_string(s, CSE_VALUE, headers[i].val);
    }
  }
}

static void
write_added_headers(stream_t *s, request_rec *r)
{
  apr_array_header_t *header = apr_table_elts(r->subprocess_env);
  apr_table_entry_t *headers = (apr_table_entry_t *) header->elts;
  int i;

  for (i = 0; i < header->nelts; ++i) {
    if (! headers[i].key)
      continue;

    if (! strcmp(headers[i].key, "HTTPS") &&
	! strcmp(headers[i].val, "on")) {
      cse_write_string(s, CSE_IS_SECURE, "");
    }
    else if (! r->user && ! strcmp(headers[i].key, "SSL_CLIENT_DN"))
      cse_write_string(s, CSE_REMOTE_USER, headers[i].val);
      

    cse_write_string(s, CSE_HEADER, headers[i].key);
    cse_write_string(s, CSE_VALUE, headers[i].val);
  }
}

/**
 * Writes a response from srun to the client
 */
static int
cse_write_response(stream_t *s, int len, request_rec *r)
{
  while (len > 0) {
    int sublen;
    int writelen;
    int sentlen;

    if (s->read_offset >= s->read_length && cse_fill_buffer(s) < 0)
      return -1;

    sublen = s->read_length - s->read_offset;
    if (len < sublen)
      sublen = len;

    writelen = sublen;
    while (writelen > 0) {
      sentlen = ap_rwrite(s->read_buf + s->read_offset, writelen, r);
      if (sentlen < 0) {
	cse_close(s, "write");
	return -1;
      }

      writelen -= sublen;
    }
    
    s->read_offset += sublen;
    len -= sublen;
  }
  
  return 1;
}

/**
 * Copy data from the JVM to the browser.
 */
static int
send_data(stream_t *s, request_rec *r, int ack, int *keepalive)
{
  int code = CSE_END;
  char buf[8193];
  char key[8193];
  char value[8193];
  int i;
    
  if (cse_fill_buffer(s) < 0)
    return -1;
    
  do {
    int l1, l2, l3;
    int len;
    
    code = cse_read_byte(s);
    l1 = cse_read_byte(s) & 0xff;
    l2 = cse_read_byte(s) & 0xff;
    l3 = cse_read_byte(s);
    len = (l1 << 16) + (l2 << 8) + (l3 & 0xff);

    if (s->socket < 0)
      return -1;

    switch (code) {
    case CSE_STATUS:
      cse_read_limit(s, buf, sizeof(buf), len);
      for (i = 0; buf[i] && buf[i] != ' '; i++) {
      }
      i++;
      r->status = atoi(buf + i);
      r->status_line = apr_pstrdup(r->pool, buf + i);
      break;

    case CSE_HEADER:
      cse_read_limit(s, key, sizeof(key), len);
      cse_read_string(s, value, sizeof(value));
      if (! strcasecmp(key, "content-type"))
	r->content_type = apr_pstrdup(r->pool, value);
      else if (! strcasecmp(key, "set-cookie"))
	apr_table_add(r->headers_out, key, value);
      else
	apr_table_set(r->headers_out, key, value);
      break;

    case CSE_DATA:
      if (cse_write_response(s, len, r) < 0)
	return -1;
      break;

    case CSE_FLUSH:
      ap_rflush(r);
      break;

    case CSE_KEEPALIVE:
      *keepalive = 1;
      break;

    case CSE_SEND_HEADER:
      break;

    case -1:
      break;

    default:
      cse_skip(s, len);
      break;
    }
  } while (code > 0 && code != CSE_END && code != CSE_CLOSE && code != ack);

  return code;
}

/**
 * handles a client request
 */
static int
write_request(stream_t *s, request_rec *r, config_t *config,
              int *keepalive, int session_index)
{
  int len;
  int code;
  unsigned int request_time = r->request_time / 1000000;
  int write_length;

  write_env(s, r);
  write_headers(s, r);
  write_added_headers(s, r);

  /* read post data */
  if (ap_should_client_block(r)) {
    char buf[BUF_LENGTH];
    int isFirst = 1;

    while ((len = ap_get_client_block(r, buf, BUF_LENGTH)) > 0) {
      if (! isFirst) {
        code = send_data(s, r, CSE_ACK, keepalive);
        if (code < 0 || code == CSE_END || code == CSE_CLOSE)
          break;
      }

      cse_write_packet(s, CSE_DATA, buf, len);
      isFirst = 0;
    }
  }

  cse_write_packet(s, CSE_END, 0, 0);

  code = send_data(s, r, CSE_END, keepalive);
  write_length = s->write_length;
  if (code < 0 && ! s->sent_data) {
    LOG(("retry"));
    if (cse_open_connection(s, config, session_index, 
                            request_time, r->pool)) {
      s->write_length = write_length;
      code = send_data(s, r, CSE_END, keepalive);
      LOG(("retry resend %d", code));
    }
    else {
      LOG(("retry fail"));
      connection_error(config, config->srun_list->srun, r);
      return 0;
    }
  }
  else if (code != CSE_END) {
    LOG(("can't retry %d %d.", code, s->sent_data));
  }
  
  return code == CSE_END;
}

/**
 * Handle a request.
 */
static int
caucho_request(request_rec *r)
{
  config_t *config = cse_get_module_config(r);
  stream_t s;
  int retval;
  int keepalive = 0;
  int reuse;
  int session_index;
  char *ip;
  srun_t *srun;
  unsigned int request_time = r->request_time / 1000000;

  if ((retval = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR)))
    return retval;

  session_index = get_session_index(config, r);
  ip = r->connection->remote_ip;

  if (! cse_open_connection(&s, config, session_index,
                            request_time, r->pool)) {
    connection_error(config, config->srun_list->srun, r);

    return OK;
  }

  srun = s.srun->srun;

  cse_lock(srun->lock);
  srun->active_sockets++;
  cse_unlock(srun->lock);
  
  LOG(("req\n"));
  reuse = write_request(&s, r, config, &keepalive, session_index);
  LOG(("done\n"));

  cse_lock(srun->lock);
  srun->active_sockets--;
  cse_unlock(srun->lock);
  
  if (reuse)
    cse_recycle(&s, request_time);
  else
    cse_close(&s, "no reuse");

  return OK;
}

/**
 * Print the statistics for each JVM.
 */
static void
jvm_status(config_t *config, request_rec *r)
{
  int i;
  stream_t s;

  ap_rputs("<center><table border=2 width='80%'>\n", r);
  ap_rputs("<tr><th>Host</th>\n", r);
  ap_rputs("    <th>Active</th>\n", r);
  ap_rputs("    <th>Pooled</th>\n", r);
  ap_rputs("    <th>Connect<br>Timeout</th>\n", r);
  ap_rputs("    <th>Live<br>Time</th>\n", r);
  ap_rputs("    <th>Dead<br>Time</th>\n", r);
  ap_rputs("</tr>\n", r);

  for (i = 0; i < config->srun_size; i++) {
    srun_item_t *srun_item = config->srun_list + i;
    srun_t *srun = srun_item->srun;
    int port;
    int pool_count;

    if (! srun)
      continue;
    
    port = srun->port;
    pool_count = ((srun->conn_head - srun->conn_tail + CONN_POOL_SIZE) %
                  CONN_POOL_SIZE);

    ap_rputs("<tr>", r);

    if (! cse_open(&s, config, srun_item, r->pool, 1)) {
      ap_rprintf(r, "<td bgcolor='#ff6666'>%d. %s:%d%s (down)</td>",
                 srun_item->session + 1,
                 srun->hostname ? srun->hostname : "localhost",
                 port, srun_item->is_backup ? "*" : "");
    }
    else {
      ap_rprintf(r, "<td bgcolor='#66ff66'>%d. %s:%d%s (ok)</td>",
                 srun_item->session + 1,
                 srun->hostname ? srun->hostname : "localhost",
                 port, srun_item->is_backup ? "*" : "");
    }

    cse_close(&s, "caucho-status");
    LOG(("close\n"));

    ap_rprintf(r, "<td align=right>%d</td><td align=right>%d</td>",
               srun->active_sockets, pool_count);
    ap_rprintf(r, "<td align=right>%d</td><td align=right>%d</td><td align=right>%d</td>",
               srun->connect_timeout, srun->live_time, srun->dead_time);
    ap_rputs("</tr>\n", r);
  }
  ap_rputs("</table></center>\n", r);
}

/**
 * Print a summary of the configuration so users can understand what's
 * going on.  Ping the server to check that it's up.
 */
static int
caucho_status(request_rec *r)
{
  config_t *config;
  web_app_t *app;
  location_t *loc;
 
  if (! r->handler || strcmp(r->handler, "caucho-status"))
    return DECLINED;

  config = cse_get_module_config(r);
  
  r->content_type = "text/html";
  if (r->header_only)
    return OK;

  ap_rputs("<html><title>Status : Caucho Servlet Engine</title>\n", r);
  ap_rputs("<body bgcolor=white>\n", r);
  ap_rputs("<h1>Status : Caucho Servlet Engine</h1>\n", r);

  if (! config)
    return OK;

  if (config->error)
    ap_rprintf(r, "<h2 color='red'>Error : %s</h2>\n", config->error);
  
  jvm_status(config, r);

  ap_rputs("<p><center><table border=2 cellspacing=0 cellpadding=2 width='80%'>\n", r);
  ap_rputs("<tr><th>Host\n", r);
  ap_rputs("    <th>url-pattern\n", r);
  
  app = config ? config->applications : 0;
  for (; app; app = app->next) {
    for (loc = app->locations; loc; loc = loc->next) {
      char port[16];
      if (app->port > 0)
        sprintf(port, ":%d", app->port);
      else
        port[0] = 0;
    
      ap_rprintf(r, "<tr bgcolor='#ffcc66'><td>%s%s%s<td>%s%s%s%s%s</tr>\n", 
                 app->host && app->host[0] ? app->host : "",
                 port,
                 *app->prefix ? app->prefix : "/",
                 loc->prefix,
                 ! loc->is_exact && ! loc->suffix ? "/*" : 
                 loc->suffix && loc->prefix[0] ? "/" : "",
                 loc->suffix ? "*" : "",
                 loc->suffix ? loc->suffix : "",
                 loc->ignore ? " (ignore)" : "");
    }
  }

  ap_rputs("</table></center>\n", r);
  ap_rputs("<hr>", r);
  ap_rprintf(r, "<em>%s<em>", VERSION);
  ap_rputs("</body></html>\n", r);
  ap_rputs("</body></html>\n", r);

  return OK;
}

/**
 * Look at the request to see if Caucho should handle it.
 */
static int
cse_dispatch(request_rec *r)
{
  config_t *config = cse_get_module_config(r);
  const char *host = ap_get_server_name(r);
  const char *uri = r->uri;
  char *url_rewrite;
  unsigned int request_time = r->request_time / 1000000;
  int len;

  if (config == NULL || ! uri)
    return DECLINED;

  LOG(("[%d] host %s\n", getpid(), host ? host : "null"));
  
  cse_update_config(config, request_time);
  
  /* Check for exact virtual host match */
  if (cse_match_request(config, host, ap_get_server_port(r), uri, 0)) {
    LOG(("[%d] match %s:%s\n", getpid(), host ? host : "null", uri));

    return caucho_request(r);
  }

  len = strlen(uri);

  if (! config->disable_caucho_status &&
      len >= sizeof("/caucho-status") - 1 &&
      ! strcmp(uri + len - sizeof("/caucho-status") + 1, "/caucho-status")) {
    r->handler = "caucho-status";
    return caucho_status(r);
  }
  
  if (config->session_url_prefix) {
    char *new_uri;
    
    new_uri = strstr(uri, config->session_url_prefix);
    if (new_uri)
      *new_uri = 0;
  }
  
  /* Strip session encoding from static files. */
  if (r->filename && config->session_url_prefix) {
    url_rewrite = strstr(r->filename, config->session_url_prefix);
    
    if (url_rewrite) {
      *url_rewrite = 0;

      /*
      if (stat(r->filename, &r->finfo) < 0)
        r->finfo.st_mode = 0;
      */
    }
  }

  return DECLINED;
}

/*
 * Only needed configuration is pointer to resin.conf
 */
static command_rec caucho_commands[] = {
    {"CauchoConfigFile", cse_config_file_command, NULL,
     RSRC_CONF|ACCESS_CONF, TAKE1,
     "Pointer to the Caucho configuration file."},
    {NULL}
};

static void caucho_register_hooks(apr_pool_t *p)
{
  ap_hook_handler(cse_dispatch, NULL, NULL, APR_HOOK_MIDDLE);
}

/* Dispatch list for API hooks */
module AP_MODULE_DECLARE_DATA caucho_module = {
    STANDARD20_MODULE_STUFF, 
    cse_create_dir_config, /* create per-dir    config structures */
    NULL,                  /* merge  per-dir    config structures */
    NULL,                  /* create per-server config structures */
    NULL,                  /* merge  per-server config structures */
    caucho_commands,       /* table of config file commands       */
    caucho_register_hooks  /* register hooks                      */
};

