/* Copyright 1999-2004 The Apache Software Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * mod_hesiod.c
 * based on mod_hesiod.c for apache 1.3 by Matthew Gray
 * and Yoav Yerushalmi.  Adapted from mod_userdir.c from
 * apache 2.
 */

#include <hesiod.h>
#include <stdio.h>
#include <sys/stat.h>
/* Note: this may differ across AFS versions */
#define AFS_ST_DEV 0x1234
#define TOKSEP " \t\r\n"

#include "apr_strings.h"
#include "apr_user.h"
#define APR_WANT_STRFUNC
#include "apr_want.h"
#if APR_HAVE_UNISTD_H
#include <unistd.h>
#endif

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

#if !defined(WIN32) && !defined(OS2) && !defined(BEOS) && !defined(NETWARE)
#define HAVE_UNIX_SUEXEC
#endif

#ifdef HAVE_UNIX_SUEXEC
#include "unixd.h"        /* Contains the suexec_identity hook used on Unix */
#endif

module AP_MODULE_DECLARE_DATA hesiod_module;
typedef struct {
  char hesiod_on;
  char afs_hesiod_checking_on;
} hesiod_server_conf;

static void *context;

/*
 * Server config for this module: global disablement flag, a list of usernames
 * ineligible for UserDir access, a list of those immune to global (but not
 * explicit) disablement, and the replacement string for all others.
 */

static void *create_hesiod_config(apr_pool_t *p, server_rec *s)
{
  hesiod_server_conf *hsc = (hesiod_server_conf *)apr_palloc(p,sizeof(hesiod_server_conf));
  hsc->hesiod_on = 0;
  hsc->afs_hesiod_checking_on = 0;  
  if (hesiod_init(&context) != 0) {
    /* is this really the right way to error out here? */
    return NULL;
  }
  return hsc;
}

static const char *set_hesiod(cmd_parms *cmd, void *dummy, const char *arg)
{
    hesiod_server_conf *hsc = ap_get_module_config(cmd->server->module_config,
						   &hesiod_module);
    hsc->hesiod_on = 1;
    if (arg) {
      if (strstr(arg,"All")) {
	hsc->hesiod_on = 1;
      } else if (strstr(arg,"AFS")) {
	hsc->afs_hesiod_checking_on = 1;
	hsc->hesiod_on = 1;
      } else {
	return "Hesiod only takes one optional arg: AFS";
      }
    }
    return NULL;
}

static const command_rec hesiod_cmds[] = {
    AP_INIT_RAW_ARGS("Hesiod", set_hesiod, NULL, RSRC_CONF,
                     "Hesiod [AFS]"),
    {NULL}
};

/* parse_hes
 *   string   -- string to parse
 * output:  (pointers in string unless type converted)
 *   type     -- type (AFS, NFS, etc)
 *   path     -- full path (/afs/athena.mit.edu/user/..../
 *   bits     -- bits (rw)
 *   srt    -- short path (/mit/yoav)
 *   priority -- (int number)
 */
void parse_hes(char *string, char **type, char **path, char **bits, char **srt, int *priority)
{
  char *temp;

  *type  = strtok(string, TOKSEP);
  *path  = strtok(NULL, TOKSEP);
  *bits  = strtok(NULL, TOKSEP);
  *srt = strtok(NULL, TOKSEP);
  if (!(temp = strtok(NULL, TOKSEP)))
    *priority = 0;
  else
    *priority = atoi(temp);
}

/* lookup_locker -- translate a locker name into an AFS path
 *   locker --  name of the locker
 *   returns:   AFS path (or NULL on failure)
 */
static char* lookup_locker (apr_pool_t *p, char* locker)
{
  char **cpp = NULL, **origcpp;
  char *type, *path, *bits, *srt;
  int  priority;
  char *ret = NULL;
  int previous_result = INT_MAX;
  struct stat sb;

  cpp = hes_resolve(locker, "filsys");
  if (!cpp) 
    return NULL;
  origcpp = cpp;
  while (*cpp) {
    parse_hes(*cpp, &type, &path, &bits, &srt, &priority);
    if (type && path && *path &&  (previous_result > priority) && !strcmp(type,"AFS") && !stat(path, &sb)) {
#ifdef SKIP_ATHENA_CELL
      if (!strncmp(path,"/afs/athena.mit.edu",19))
        path += 19;            /* skip "/afs/athena.mit.edu" */
#endif
      ret = (char*)apr_palloc(p, strlen(path)+1);
      /* for now, we'll fail without indicating what happened if we get NULL */
      if (!ret) {
	hesiod_free_list(context,origcpp);
	return NULL;
      } 
      strcpy(ret, path);
      previous_result = priority;
    }
    *cpp++;
  }
  hesiod_free_list(context,origcpp);
  return(ret);
}


/* get_path_into_locker -- translate a locker URL into a full AFS gateway path
 *   current --  current request path
 *   returns:    translated path
 * NOTE: most of this code was taken from CERN's get_user_dir() in HTConfig.c
 */
char* get_path_into_locker (request_rec *r, int *stat_enodev)
{
  int statr;
  struct stat finfo;
  /* lockername = substr(uri, 2, index(uri, "/")); */
    char * lockername = ((r)->uri)+2;
    char * end = strchr(lockername, '/');

    *stat_enodev = 0;

    if (*lockername) {
        char* lpath;

	if (end)
	  *end++ = 0;
	lpath = lookup_locker(r->pool, lockername);
	if (end) {
	  *(--end) = '/';
	  *end++;
	}
        if (lpath) {
            int homelen = strlen(lpath);
            char * d = (char*)apr_palloc(r->pool, homelen +
					  (end ? strlen(end) : 0) + 6);

	    if(!d) return NULL;

            strcpy(d, lpath);
            if (lpath[homelen-1] != '/')
                strcat(d, "/");
	    strcat(d, "www/");
            if (end) {
                strcat(d, end);
            }
	    statr=stat(d,&finfo);
	    if(statr != 0){
	      if (errno == ENODEV){
		*stat_enodev = 1;
	      }
	      return NULL;
	    }
            return d;
        }
    }
    return NULL;
}


int translate_hesiod(request_rec *r)
{
  char *ret;
  char *thefile;
  char *euritmp, *uritmp;
  char *end;
  int statr;
  int stat_enodev;
  struct stat finfo;
  void *sconf = r->server->module_config;
  hesiod_server_conf *hsc = (hesiod_server_conf *) ap_get_module_config(sconf, &hesiod_module);

  if(hsc->hesiod_on){
    if(r->uri[1] != '~')
      return DECLINED;
    if(!strchr((r->uri)+1, '/')){
      uritmp = apr_palloc(r->pool, (strlen(r->uri) + strlen(r->server->server_hostname) +30));
      if (ntohs(r->connection->local_addr->port) != 80)
	sprintf(uritmp, "http://%s:%i%s/", r->server->server_hostname,
		(int)r->connection->local_addr->port,r->uri);
      else
	sprintf(uritmp, "http://%s%s/", r->server->server_hostname, r->uri);	
      apr_table_set(r->headers_out, "Location", uritmp);
      return HTTP_MOVED_TEMPORARILY;
    }
    if(ret=get_path_into_locker(r, &stat_enodev)) {
      statr = stat(ret, &finfo);
      if (S_ISDIR(finfo.st_mode) && ret[strlen(ret) - 1] != '/') {
        uritmp = apr_palloc(r->pool, (strlen(r->uri) + strlen(r->server->server_hostname) +30));
        if (ntohs(r->connection->local_addr->port) != 80)
          sprintf(uritmp, "http://%s:%i%s/", r->server->server_hostname,
                  (int)ntohs(r->connection->local_addr->port), r->uri);
        else
          sprintf(uritmp, "http://%s%s/", r->server->server_hostname, r->uri);	
        apr_table_set(r->headers_out, "Location", uritmp);
        return HTTP_MOVED_TEMPORARILY;
      }
      r->uri = ret;

      if(hsc->afs_hesiod_checking_on){
	statr = stat(ret, &finfo);
	if((statr == -1) || (finfo.st_dev != AFS_ST_DEV)){
	  return HTTP_FORBIDDEN;
	}
      }
    }
#ifndef IGNORE_ENODEV
    else if (stat_enodev == 1) {
      /* stat returned -1 but it may exist in a disallowed AFS cell */
      uritmp = apr_palloc(r->pool, strlen(r->uri) + strlen("http://lost-contact.mit.edu") + 1);
      sprintf(uritmp, "http://lost-contact.mit.edu%s", r->uri);
      apr_table_set(r->headers_out, "Location", uritmp);
      return HTTP_MOVED_TEMPORARILY;
    }
#endif
  }
  return DECLINED;
}

static void register_hooks(apr_pool_t *p)
{
    static const char * const aszPre[]={ "mod_alias.c",NULL };
    static const char * const aszSucc[]={ "mod_vhost_alias.c",NULL };

    ap_hook_translate_name(translate_hesiod,aszPre,aszSucc,APR_HOOK_MIDDLE);
}

module AP_MODULE_DECLARE_DATA hesiod_module = {
    STANDARD20_MODULE_STUFF,
    NULL,                       /* dir config creater */
    NULL,                       /* dir merger --- default is to override */
    create_hesiod_config,      /* server config */
    NULL,                       /* merge server config */
    hesiod_cmds,               /* command apr_table_t */
    register_hooks              /* register hooks */
};
