/* ====================================================================
 * Copyright (c) 1995 The Apache Group.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. All advertising materials mentioning features or use of this
 *    software must display the following acknowledgment:
 *    "This product includes software developed by the Apache Group
 *    for use in the Apache HTTP server project (http://www.apache.org/)."
 *
 * 4. The names "Apache Server" and "Apache Group" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission.
 *
 * 5. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by the Apache Group
 *    for use in the Apache HTTP server project (http://www.apache.org/)."
 *
 * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE APACHE GROUP OR
 * IT'S CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Group and was originally based
 * on public domain software written at the National Center for
 * Supercomputing Applications, University of Illinois, Urbana-Champaign.
 * For more information on the Apache Group and the Apache HTTP server
 * project, please see <http://www.apache.org/>.
 *
 */


/********
 *
 * Mod_Auth_External version 2.2.8
 *
 * Original External authentication module by Nathan Neulinger <nneul@umr.edu>
 *
 * Versions through 2.0.1 by Nathan Neulinger <nneul@umr.edu>
 *                       and Tyler Allison <allison@nas.nasa.gov>
 *
 * Versions after 2.1.0 by Jan Wolter <jan@unixpapa.com>
 *
 * Port to Apache 2.0 by Dave Woolaway <dave@watersheep.org>
 *                   and Sven Koch <haegar@sdinet.de>


Add to server configuration file (normally httpd.conf):
-------------------------------------------------------

	AddExternalAuth <keyword> <path-to-authenticator>
	AddExternalGroup <keyword> <path-to-group-checker>

	SetExternalAuthMethod <keyword> <method>
	SetExternalGroupMethod <keyword> <method>

 The AddExternalAuth and AddExternalGroup directives specify the full pathnames
 of the external programs to be used to check user and group authentication
 for the given external authentication keywords.

 The SetExternalAuthMethod and SetExternalGroupMethod directives specify how
 the data is to be passed to the external authentication programs. There are
 currently three possible methods:

	environment     use environment variables. (default)
	pipe            write newline-terminated strings to stdin.
	checkpassword   write null-terminated strings to filedescriptor 3.
	function        call an internal function instead of external program.

 For "environment", "pipe" and "checkpassword", the option specifies a
 program to run.  We do not start a shell to run the command, so full path
 must be given and no special shell characters (wildcards, I/O redirection,
 etc.) can be used.  If you need these, write an sh-script wrapper for
 your authentication program.

 Checkpassword is a standard protocol sometimes used for mail systems.  See
 http://cr.yp.to/checkpwd.html .  Various different authenticators using this
 protocol are available on the net.  The checkpassword protocol does not
 support group checking, but we do it anyway.

 For the "function" method to work, you must #define _HARDCODE_ and code your
 function into this module.  The <path-to-authenticator> argument is treated
 as a control argument to that function, normally of the form:
        AddExternalAuth <keyword> <type>:<config file>

Usage in auth config files:
---------------------------

	AuthExternal <keyword>
	AuthExternal afs
	GroupExternal <keyword>
	GroupExternal unix

	AuthExternalAuthoritative on|off
	AuthExternalGroupsAtOnce on|off

 Obviously, the keywords given must match those in the config files.

 The AuthExternalAuthoritative controls whether or not there is fall-through
 to other authentication methods.  If it is off, and the user's login and
 password are not accepted by the external authentication program, then that
 same login and password can be checked by other authentication methods
 (e.g., basic authentication with htpasswd files or dbm files).  If it is
 "on" there is no fall through.  It defaults to "on".  This is a change
 from earlier versions of mod_auth_external which were never authoritative.

 Originally when an external group authenticator was used, and more than one
 allowed group was listed, then the external authenticator would be run once
 to check each group.  Now all groups are sent as a list.   The old behavior
 can be restored by setting "AuthExternalGroupsAtOnce off".  It defaults to
 "on".

 If mod_auth_external is the only authentication method configured in a
 directory then it doesn't much matter if it is authoritative or not.  However,
 if you are using the GroupExternal and "require group ..." directives, then
 you should do "AuthExternalAuthoritative on".  Otherwise access failures get
 a server error page instead of an authentication denied page because there
 is no authoritative group authenticator.

Implementation of external authentication programs:
---------------------------------------------------

 How the external authentication program gets its arguments depends on
 the method used.  In the "environment" method, the arguments are passed
 in environment variables.  AuthExternals are passed the user id and the clear-
 text password in the USER and PASS environment variables.  GroupExternals are
 passed the userid and group name in the USER and GROUP environment variables.

 In the "pipe" method, the external authentication program reads its inputs
 from standard input.  AuthExternals will find the user id on the first line
 and the clear-text password on the second.  GroupExternals will find the
 user id on the first line and the group name on the second.

 In all cases, the external authentication should return 0 if the given
 password is correct for the given user (or if the given user is in the
 given group), and a non-zero error code if not.

 Whichever method is used, the environment variable IP will be set to the
 client's ip-address, and the environment variable HOST will be set to the
 client's hostname.  These may be useful for logging.  Note that if you have
 configured "HostnameLookups Off" then HOST will usually not be set.  If
 you really want hostnames, either turn on HostnameLookups or do your own
 gethostbyaddr() calls from the authenticator when HOST is not defined.

 The external authenticator will also be passed a copy of the httpd's
 PATH environment variable.  However, this is just whatever PATH was in
 effect when it was launched, and may differ if the server was launched
 automatically during a reboot or manually by an admin.  Probably your
 program should set its own PATH if it needs one.

 If the authenticator is a script rather than a compiled program, it normally
 has to start with a "#!/bin/sh" or "#!/usr/bin/perl" type directive.  Scripts
 without such directives may get interpreted by the shell, or may just not
 work, depending on your installation.

Security considerations:
------------------------

 The "environment" method of passing arguments is insecure on many versions
 of Unix.  Some versions of Unix (including SunOS and IRIX) allow any user
 to run "ps -e" commands to inspect the environment variables of any running
 process.  This would allow any user of the system to see logins and clear-
 text passwords in the USER and PASS variables of authenticator processes.
 Some versions of Unix (including Linux) only allow users to see environment
 variables on their own processes, but even this could be a problem if
 untrusted users can put CGI programs on your server, since those CGI programs
 may run as the same user as the authenticator processes.  The "pipe" method
 should be used to avoid these problems.

 The authenticator program should be written with great care.  It should not
 make any assumptions about the maximum length of logins and passwords.
 It should take steps to avoid dumping core (on some systems core files are
 publically readable, and may contain part of your password database).  Etc.
 You get points for paranoia.

Performance considerations:
---------------------------

 Using an external authenticator is always slower than hardcoding the
 authenticator into the module.

Comments/questions/etc. to janc@cyberspace.org

********/


/* Uncomment if you want to use a HARDCODE'd check (default off) */
/* #define _HARDCODE_ */

#ifdef _HARDCODE_
  /* Uncomment if you want to use your own Hardcode (default off) */
  /*             MUST HAVE _HARDCODE_ defined above!                */
  /* #include "your_function_here.c" */
#endif


#include "apr_lib.h"

#define APR_WANT_STRFUNC
#include "apr_want.h"
#include "apr_strings.h"

#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_log.h"
#include "http_protocol.h"
#include "http_request.h"   /* for ap_hook_(check_user_id | auth_checker)*/
#include <sys/wait.h>
#include <signal.h>
#ifdef APR_HAVE_UNISTD_H
#include <unistd.h>
#endif

// Apache 1.3.0 had a conveninent version number to check.  2.0.0 seems not to.
//#if !defined(APACHE_RELEASE) || (APACHE_RELEASE < 2000000)
//#error This module requires Apache 2.0.0 or later.
//#endif

/* Names of environment variables used to pass data to authenticator */
#define ENV_USER	"USER"
#define ENV_PASS	"PASS"
#define ENV_GROUP	"GROUP"
#define ENV_URI		"URI"
#define ENV_IP		"IP"
#define ENV_HOST	"HOST"		/* Remote Host */
#define ENV_HTTP_HOST   "HTTP_HOST"	/* Local Host */
/* Define this if you want cookies passed to the script */
#define ENV_COOKIE	"COOKIE"

/* Maximum number of arguments passed to an authenticator */
#define MAX_ARG 32

/*
 * Structure for the module itself
 */
module AP_MODULE_DECLARE_DATA external_auth_module;

/*
 *  Data types for per-dir and server configuration
 */
typedef struct
{
    char *auth_extname;
    char *group_extname;
    int  external_authoritative;
    int  external_groupsatonce;
} extauth_dir_config_rec;

typedef struct
{
    apr_table_t *auth_extpath;
    apr_table_t *auth_extmethod;

    apr_table_t *group_extpath;
    apr_table_t *group_extmethod;
} extauth_server_config_rec;


/*
 * Creators for per-dir and server configurations
 */

static void *create_extauth_dir_config(apr_pool_t *p, char *d)
{
    extauth_dir_config_rec *r= apr_pcalloc(p, sizeof(extauth_dir_config_rec));

    r->external_authoritative= 1;	/* strong by default */
    r->external_groupsatonce= 1;	/* default to on */
    return r;
}


static void *create_extauth_server_config( apr_pool_t *p, server_rec *s)
{
    extauth_server_config_rec *scr;

    scr= (extauth_server_config_rec *) apr_palloc(p,
    	sizeof(extauth_server_config_rec) );

    scr->auth_extmethod=  apr_table_make(p, 4);
    scr->auth_extpath=    apr_table_make(p, 4);
    scr->group_extmethod= apr_table_make(p, 4);
    scr->group_extpath=   apr_table_make(p, 4);

    return (void *)scr;
}


/*
 * Handler for a AddExternalAuth server config line - add a external auth
 * type to the server configuration
 */

static const char *add_extauth(cmd_parms *cmd, void *dummy, const char *keyword,
				const char *path)
{
    extauth_server_config_rec *sc_rec;

    sc_rec= ap_get_module_config( cmd->server->module_config,
	&external_auth_module);

    apr_table_set( sc_rec->auth_extpath, keyword, path );

    apr_table_set( sc_rec->auth_extmethod, keyword, "environment" );

    return NULL;
}

/*
 * Handler for a AddExternalGroup server config line - add a external group
 * type to the server configuration
 */

static const char *add_extgroup(cmd_parms *cmd, void *dummy,
			 	const char *keyword, const char *path)
{
    extauth_server_config_rec *sc_rec;

    sc_rec= ap_get_module_config( cmd->server->module_config,
	&external_auth_module);

    apr_table_set( sc_rec->group_extpath, keyword, path );

    apr_table_set( sc_rec->group_extmethod, keyword, "environment" );

    return NULL;
}

/*
 * Handler for a SetExternalAuthMethod server config line - change an external
 * auth method in the server configuration
 */

static const char *set_extauth_method(cmd_parms *cmd, void *dummy,
					const char *keyword, const char *method)
{
	extauth_server_config_rec *sc_rec;

	sc_rec= ap_get_module_config( cmd->server->module_config,
	    &external_auth_module);

	apr_table_set( sc_rec->auth_extmethod, keyword, method );
	
	return NULL;
}

/*
 * Handler for a SetExternalGroupMethod server config line - change an external
 * group method in the server configuration
 */

static const char *set_extgroup_method(cmd_parms *cmd, void *dummy,
					const char *keyword, const char *method)
{
	extauth_server_config_rec *sc_rec;

	sc_rec= ap_get_module_config( cmd->server->module_config,
	    &external_auth_module);

	apr_table_set( sc_rec->group_extmethod, keyword, method );
	
	return NULL;
}


/*
 * Commands that this module can handle
 */

static const command_rec extauth_cmds[] =
{
	AP_INIT_TAKE1("AuthExternal", ap_set_string_slot,
	    (void *)APR_OFFSETOF(extauth_dir_config_rec,auth_extname),
	    OR_AUTHCFG,
	    "a keyword indicating which authenticator to use"),

	AP_INIT_TAKE2("AddExternalAuth", add_extauth, NULL,
	    RSRC_CONF,
	    "a keyword followed by a path to the authenticator program"),
	    
	AP_INIT_TAKE2("SetExternalAuthMethod", set_extauth_method, NULL,
	    RSRC_CONF,
	    "a keyword followed by the method by which the data is passed"),

	AP_INIT_TAKE1("GroupExternal", ap_set_string_slot,
	    (void *)APR_OFFSETOF(extauth_dir_config_rec,group_extname),
	    OR_AUTHCFG,
	    "a keyword indicating which group checker to use"),
		
	AP_INIT_TAKE2("AddExternalGroup", add_extgroup, NULL,
	    RSRC_CONF,
	    "a keyword followed by a path to the group check program"),

	AP_INIT_TAKE2("SetExternalGroupMethod", set_extgroup_method, NULL,
	    RSRC_CONF, 
	    "a keyword followed by the method by which the data is passed"),

	AP_INIT_FLAG("AuthExternalAuthoritative", ap_set_flag_slot,
	    (void *)APR_OFFSETOF(extauth_dir_config_rec,
		external_authoritative),
	    OR_AUTHCFG,
	    "Set to 'off' to allow access control to be passed along to lower "
	    "modules if the UserID is not known to this module" ),

	AP_INIT_FLAG("AuthExternalGroupsAtOnce", ap_set_flag_slot,
	    (void *)APR_OFFSETOF(extauth_dir_config_rec, external_groupsatonce),
	    OR_AUTHCFG,
	    "Set to 'off' if group authenticator cannot handle multiple group "
	    "names in one invocation" ),

	{ NULL }
};


/* Lookup a header in the request - this routine lifted from mod_rewrite.c
 */

static char *lookup_header(const request_rec *r, const char *name)
{
    const apr_array_header_t *hdrs_arr;
    const apr_table_entry_t *hdrs;
    int i;

    hdrs_arr= apr_table_elts(r->headers_in);
    hdrs= (const apr_table_entry_t *)hdrs_arr->elts;
    for (i= 0; i < hdrs_arr->nelts; ++i)
    {
	if (hdrs[i].key != NULL && strcasecmp(hdrs[i].key, name) == 0)
	    return hdrs[i].val;
    }
    return NULL;
}


/* Run an external authentication program using either the "pipe",
 * "checkpasswd" or "environment" method to pass the arguments.
 */

static int exec_external(const char *extpath, const char *extmethod,
		const request_rec *r, const char *dataname, const char *data)
{
    int pipe_to_auth[2];
    int pid, status;
    conn_rec *c= r->connection;
    const char *remote_host;
    int usecheck= extmethod && !strcasecmp(extmethod, "checkpassword");
    int usepipe= usecheck || (extmethod && !strcasecmp(extmethod, "pipe"));

    if (usepipe && pipe(pipe_to_auth))

	/* pipe() failed - weird */
	return -3;

    if ( (pid= fork()) < 0 )
    {
	/* fork() failed - weird */
	if (usepipe)
	{
	    close(pipe_to_auth[0]);
	    close(pipe_to_auth[1]);
	}
	return -4;
    }
    else if (pid == 0)
    {
	/* We are the child process */

	char *child_env[11];
	char *child_arg[MAX_ARG+2];
	const char *t;
	char *cookie, *host;
	int i= 0;

	/* Put PATH, hostname, ip address, uri, etc into environment */
	child_env[i++]= apr_pstrcat(r->pool, "PATH=", getenv("PATH"), NULL);

	remote_host= ap_get_remote_host(c, r->per_dir_config, REMOTE_HOST, NULL);
	child_env[i++]= apr_pstrcat(r->pool, "AUTHTYPE=", dataname, NULL);
	if (remote_host != NULL)
	    child_env[i++]= apr_pstrcat(r->pool, ENV_HOST"=", remote_host, NULL);

	if (c->remote_ip)
	    child_env[i++]= apr_pstrcat(r->pool, ENV_IP"=", c->remote_ip, NULL);

	if (r->uri)
	    child_env[i++]= apr_pstrcat(r->pool, ENV_URI"=", r->uri, NULL);

	if ((host= lookup_header(r,"Host")) != NULL)
	    child_env[i++]= apr_pstrcat(r->pool, ENV_HTTP_HOST"=", host, NULL);

#ifdef ENV_COOKIE
	if ((cookie= lookup_header(r,"Cookie")) != NULL)
	    child_env[i++]= apr_pstrcat(r->pool, ENV_COOKIE"=", cookie, NULL);
#endif

	/* Direct stdout and stderr to log file */
	ap_error_log2stderr(r->server);
	dup2(2,1);

	/* Close any open file descriptors and such in the pool. This won't
	 * close the pipe because it isn't in the pool.  It will close
	 * anything that might be on fd 3, which is important to do before
	 * we try to attach the pipe to that in the checkpassword case.  */
	apr_pool_cleanup_for_exec();

	if (usepipe)
	{
	    /* Connect stdin to pipe */
	    dup2(pipe_to_auth[0], usecheck ? 3 : 0);
	    close(pipe_to_auth[0]);
	    close(pipe_to_auth[1]);
	}
	else
	{
	    /* Put user name and password/group into environment */
	    child_env[i++]= apr_pstrcat(r->pool, ENV_USER"=", r->user, NULL);
	    child_env[i++]= apr_pstrcat(r->pool, dataname, "=", data, NULL);
	}

	/* End of environment */
	child_env[i]= NULL;

	/* Construct argument array */
	for (t= extpath, i=0; t[0] && (i <= MAX_ARG + 1);
	     child_arg[i++]= ap_getword_white(r->pool, &t)) {}
	child_arg[i]= NULL;

	/* Overwrite ourselves with the authenticator program */
	execve(child_arg[0], child_arg, child_env);

	/* If execve failed: */ exit(-1);
    }
    else
    {
	/* We are the parent process */

	if (usepipe)
	{
	    close(pipe_to_auth[0]);
	  
	    /* Send the user */
	    write(pipe_to_auth[1], r->user, strlen(r->user));
	    write(pipe_to_auth[1], usecheck ? "\0" : "\n", 1);
	  
	    /* Send the password */
	    write(pipe_to_auth[1], data, strlen(data));
	    write(pipe_to_auth[1], usecheck ? "\0" : "\n", 1);

	    /* Send dummy timestamp for checkpassword */
	    if (usecheck) write(pipe_to_auth[1], "0", 2);

	    /* Close output */
	    close(pipe_to_auth[1]);
	}
      
	/* Await a response */
	waitpid(pid, &status, 0);
	return WIFEXITED(status) ? WEXITSTATUS(status) : -2;
    }
}


/*
 * Authenticate a user
 */

static int extauth_basic_user(request_rec *r)
{
    extauth_dir_config_rec *dir_config_rec=
	(extauth_dir_config_rec *)ap_get_module_config(r->per_dir_config,
	&external_auth_module);

    extauth_server_config_rec *server_config_rec=
	(extauth_server_config_rec *)ap_get_module_config(r->server->module_config,
	&external_auth_module);

    const char *sent_pw;
    int res, code= 1;
    const char *extname;
    const char *extpath, *extmethod;

    /* Get the password, exit if can't get */
    if ((res= ap_get_basic_auth_pw(r, &sent_pw)))
	    return res;

    /* Extract which external was chosen */
    extname= dir_config_rec->auth_extname;

    /* Check if we are supposed to handle this authentication */
    if ( !extname )
	return DECLINED;

    /* Get the path associated with that external */
    if (!(extpath= apr_table_get(server_config_rec->auth_extpath, extname)))
    {
	errno= 0;
	ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
			"invalid AuthExternal keyword (%s)", extname);
	ap_note_basic_auth_failure(r);
	return HTTP_UNAUTHORIZED;
    }

    /* Get the Method requested */
    extmethod= apr_table_get(server_config_rec->auth_extmethod, extname);

    if ( extmethod && !strcasecmp(extmethod, "function") )
#ifdef _HARDCODE_
    {
	char *check_type;		/* Pointer to HARDCODE type check  */
	char *config_file;		/* Pointer to HARDCODE config file */
	int standard_auth= 0;

	/* Parse a copy of extpath into type and filename */
	check_type= apr_pstrdup(r->pool, extpath);
	config_file= strchr(check_type, ':');
	
	if (config_file == NULL) {        /* No colon! httpd.conf is wrong */
	    errno= 0;
	    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
	    	"Problem parsing config file: "
	    	"(%s) directive. You must have a ':' "
	    	"seperator!\n", check_type);
	    	
	    return HTTP_INTERNAL_SERVER_ERROR;
	}
	*config_file= '\0';		    /* Mark end of type */
	config_file++;                      /* Start of filename */

	/* This is where you make your function call.         */
	/* Here is an example of what one looks like.         */
	/*                                                    */
	/* if (strcmp(check_type,"RADIUS")==0)                */
	/*   code= radcheck(r->user,sent_pw,config_file);     */
	/*                                                    */
	/* Replace 'RADIUS' with whatever you are using as    */
	/* the <type> in:                                     */
	/* AddExternalAuth <keyword> <type>:<config file>     */
	/*                                                    */
	/* Replace radcheck with whatever the name of your    */
	/* function is.                                       */
	/* Note: If you dont use a config_file you must at    */
	/* least include the ':'..for example..at a minimum   */
	/* you must have 'RADIUS:' in the httpd.conf for your */
	/* function to be called.                             */

	else
	{
	    errno= 0;
	    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
		"Problem parsing config file: "
		"(%s) directive (%s) config_file\n",
		check_type, config_file);
	    return HTTP_INTERNAL_SERVER_ERROR;
	}
    }
#else
	  /* If _HARDCODE_ is not defined then no matter what the user says */
	  /* about wanting to use a function we fail!!                      */
	  code= -4;
#endif
    else
    	code= exec_external(extpath, extmethod, r, ENV_PASS, sent_pw);

    if (!code) return OK;
    
    errno= 0;

    if (!dir_config_rec->external_authoritative)
    	return DECLINED;

    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
    	"AuthExtern %s [%s]: Failed (%d) for "
    	"user %s", extname, extpath, code, r->user);
    ap_note_basic_auth_failure(r);
    return HTTP_UNAUTHORIZED;
}


static int extauth_check_auth(request_rec *r) 
{
    extauth_dir_config_rec *dir_config_rec=
	(extauth_dir_config_rec *)ap_get_module_config(r->per_dir_config,
	&external_auth_module);

    extauth_server_config_rec *server_config_rec=
	(extauth_server_config_rec *)ap_get_module_config(r->server->module_config,
	&external_auth_module);

    int m= r->method_number;
    int code;
    char *extname;
    const char *extpath, *extmethod;

    const apr_array_header_t *reqs_arr= ap_requires(r);
    require_line *reqs= reqs_arr ? (require_line *)reqs_arr->elts : NULL;

    int x;
    const char *t;
    char *w;

    /* Extract which external was chosen */
    extname= dir_config_rec->group_extname;

    /* Check if we are supposed to handle this authentication */
    if ( !extname )
	return DECLINED;

    if (!reqs_arr)
    	return DECLINED;

    for(x=0; x < reqs_arr->nelts; x++)
    {
	if (!(reqs[x].method_mask & (1 << m))) continue;

	t= reqs[x].requirement;
	w= ap_getword_white(r->pool, &t);

	if(!strcmp(w,"valid-user"))
	    return OK;

	if(!strcmp(w,"user"))
	{
	    while(t[0])
	    {
		w= ap_getword_conf(r->pool, &t);
		if(!strcmp(r->user,w))
		    return OK;
	    }
	}
	else if( !strcmp(w,"group") )
	{
	    if (t[0])
	    {
		/* Get the path and method associated with that external */
		if (!(extpath= apr_table_get(
			server_config_rec->group_extpath, extname)) ||
		    !(extmethod= apr_table_get(
		    	server_config_rec->group_extmethod, extname)))
		{
		    errno= 0;
		    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
		    	"invalid GroupExternal keyword (%s)", extname);
		    ap_note_basic_auth_failure(r);
		    return HTTP_UNAUTHORIZED;
		}

		if (dir_config_rec->external_groupsatonce)
		{
		    /* Pass rest of require line to authenticator */
		    code= exec_external(extpath, extmethod, r, ENV_GROUP, t);
		    if (!code) return OK;
		}
		else
		{
		    /* Call authenticator once for each group name on line */
		    do {
		        w= ap_getword_white(r->pool, &t);
			code= exec_external(extpath,
				extmethod, r, ENV_GROUP, w);
			if (!code) return OK;
		    } while(t[0]);
		}
	    }
	}
	else if (dir_config_rec->external_authoritative)
	{
	    /* If we aren't authoritative, any require directives could be
	     * valid even if we don't grok it.  However if we are, we can
	     * warn the user that they did something wrong.
	     */
	    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
	    	"access to %s failed, reason: unknown require directive:"
	    	"\"%s\"", r->uri, reqs[x].requirement);
	}
    }
    
    if (!dir_config_rec->external_authoritative)
	return DECLINED;

    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
    	"access to %s failed, reason: user %s not allowed access",
    	r->uri, r->user);

    ap_note_basic_auth_failure(r);
    return HTTP_UNAUTHORIZED;
}


static void register_hooks(apr_pool_t *p)
{
    ap_hook_check_user_id(extauth_basic_user, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_auth_checker(extauth_check_auth, NULL, NULL, APR_HOOK_MIDDLE);
}
    

module AP_MODULE_DECLARE_DATA external_auth_module = {
    STANDARD20_MODULE_STUFF,
    create_extauth_dir_config,	  /* create per-dir config */
    NULL,			  /* merge per-dir config - dflt is override */
    create_extauth_server_config, /* create per-server config */
    NULL,			  /* merge per-server config */
    extauth_cmds,		  /* command apr_table_t */
    register_hooks		  /* register hooks */
};
