/*
 * mod_lock: implements a simple method for conditional lock.
 * useful for locking a single document tree or a virtual host without
 * affecting other servers.
 * 
 *                               Lyonel VINCENT <vincent@hpwww.ec-lyon.fr>
 * 
 * usage:
 *	SetLockFile	<filename>
 *		default value:	none
 *		context:	Directory
 *		effect:		Enables the conditional lock for a location.
 *				The web server will check for the existence of
 *				the file each time it will access the concerned
 *				documents. If it finds it, it will return a
 *				503 (Service unavailable) HTTP status code.
 *		remarks:	if <filename> is a null string (""), conditional
 *				lock will be disabled for this class of
 *				documents.
 *
 *	LockBypass	<host1> <host2> ...
 *		default value:	none
 *		context:	Directory
 *		effect:		Allow access from an host, even if locked.
 *		remarks:	hostnames can be either real hostnames
 *				(www.hp.com) or domain names (.hp.com).
 *
 * example:
 *	<Location />
 *	SetLockFile	/www/locks/root
 *	LockBypass	support.hp.com .grenoble.hp.com
 *	ErrorDocument	503 /maintenance.html
 *	</Location>
 *
 */

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

typedef struct
{
  char * hostname;
} bypass;

typedef struct
{
  char * lockfile;
  array_header * bypass;
} lock_config_rec;

module lock_module;

static const char *lock_set_lockfile (cmd_parms *cmd,
	lock_config_rec *sec, char * arg)
{
  sec->lockfile=arg;
  return NULL;
}

static const char *lock_set_bypass (cmd_parms *cmd,
	lock_config_rec *sec, char * arg)
{
  bypass * host;

  host = (bypass *)ap_push_array(sec->bypass);
  host->hostname = ap_pstrdup(cmd->pool, arg);
  return NULL;
}

static command_rec lock_cmds[] = {
	{ "SetLockFile", lock_set_lockfile, NULL, OR_AUTHCFG, TAKE1, "Filename" },
	{ "LockBypass", lock_set_bypass, NULL, OR_AUTHCFG, ITERATE, "Hostname" },
	{ NULL }
};

static void *create_lock_dir_config (pool *p, char *d)
{
  lock_config_rec * sec = (lock_config_rec *)ap_pcalloc (p, sizeof(lock_config_rec));

  if (!sec) return NULL; /* no memory... */

  sec -> lockfile = "";
  sec -> bypass = ap_make_array (p, 1, sizeof (bypass));
  return sec;
}

static void *merge_lock_dir_config (pool *p,
			void *pdir,
			void *sdir)
{
  lock_config_rec *parent_dir = (lock_config_rec *)pdir,
	*subdir = (lock_config_rec *)sdir;
  lock_config_rec *new = (lock_config_rec *)ap_palloc (p, sizeof(lock_config_rec));

  /* if a directory does not have a lock file, use the lock file of its
     parent */
  if(strlen(subdir->lockfile)) new->lockfile = ap_pstrdup(p,subdir->lockfile);
	else new->lockfile = ap_pstrdup(p,parent_dir->lockfile);

  new->bypass = ap_append_arrays(p, subdir->bypass, parent_dir->bypass);

  return (void*)new;
}

static int in_domain(const char *domain, const char *what)
{
    int dl=strlen(domain);
    int wl=strlen(what);

    if((wl-dl) >= 0) {
        if (strcasecmp(domain,&what[wl-dl]) != 0) return 0;

        /* Make sure we matched an *entire* subdomain --- if the user
         * said 'allow from good.com', we don't want people from nogood.com
         * to be able to get in.
         */

        if (wl == dl) return 1; /* matched whole thing */
        else return (domain[0] == '.' || what[wl - dl - 1] == '.');
    } else
        return 0;
}

static int lock_handler(request_rec *r)
{
  lock_config_rec *sec =
	(lock_config_rec *)ap_get_module_config(r->per_dir_config, &lock_module);
  FILE * f = NULL;
  const char * remotehost = NULL;
  bypass * hosts = (bypass*)sec->bypass->elts;
  int i = 0;
  
  if(!strlen(sec->lockfile) || 		/* conditional lock disabled */
	(r->prev &&			/* redirected through ErrorDocument */
	  (r->prev->status==HTTP_SERVICE_UNAVAILABLE))
     ) return DECLINED;

  if(!(f=ap_pfopen(r->pool,sec->lockfile,"r")))
    switch(errno)
    {
      case EACCES: break;		/* access denied */
      case EISDIR: break;		/* the file is a directory */
      default: return DECLINED;		/* lock file does not exist */
    }

  if(f) ap_pfclose(r->pool,f);

  /* now we check if the remote host is allowed to bypass the lock */

  remotehost = ap_get_remote_host(r->connection, r->per_dir_config, REMOTE_HOST);
  if(remotehost)
    for(i=0; i < sec->bypass->nelts; i++)
      if(in_domain(hosts[i].hostname, remotehost)) return DECLINED; /* access granted */

  return HTTP_SERVICE_UNAVAILABLE;	/* sorry */
}

static handler_rec lock_handlers[] =
{
	{ "*/*", lock_handler },
	{ NULL }
};

module lock_module = {
   STANDARD_MODULE_STUFF,
   NULL,			/* initializer */
   create_lock_dir_config,	/* dir config creater */
   merge_lock_dir_config,	/* dir merger --- default is to override */
   NULL,			/* server config */
   NULL,			/* merge server config */
   lock_cmds,			/* command table */
   lock_handlers,		/* handlers */
   NULL,			/* filename translation */
   NULL,			/* check_user_id */
   NULL,			/* check access rights */
   NULL,			/* check access */
   NULL,			/* type_checker */
   NULL,			/* fixups */
   NULL,			/* logger */
   NULL				/* header parser */
};
