/* Copyright (c) 2000 Ingo Luetkebohle, 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.
 *
 * THIS SOFTWARE IS PROVIDED ``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 AUTHOR OR OTHER CODE 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.
 */

/* $Id$
 * mod_auth_etc_group for apache 2.0
 *
 * Based on: v 1.1 from 07. July 2002 (for apache 1.3)
 * 
 *
 * mod_auth_etc_group:
 * 	'require group' functionality against /etc/group.
 *
 * This code was originally part of mod_auth_pam:
 * Written by Ingo Luetkebohle, based upon mod_auth.c, with contributions 
 * from Fredrik Ohrn (group performance), Dirk-Willem van Gulik (licensing
 * consultation) and Michael Johnson (example group implementation).
 * 
 * Changes:
 *   09-Jul-02: Group part split off from mod_auth_pam 2.02 (Dirk-Willem 
 *	        van Gulik, <dirkx@covalent.net>).
 *
 * usage information:
 *
 * new-style (DSO and apache 2.0)
 * 
 *	compile with 
 *		apxs -c mod_auth_etc_group.c
 *
 *		apxs -c mod_auth_etc_group.c
 *
 * 	install with
 *		apxs -i -a mod_auth_etc_group.so
 *
 * Directives
 *
 * AuthGROUP_Enabled on|off       
 *                              If on, mod_auth_etc_group will try to verify
 *				any 'require group <list of groups>' against
 *				the /etc/group file (and also check that 
 *				the user is in /etc/passwd to avoid surpizes).
 *
 *				If off, mod_auth_etc_group will DECLINE immediately.
 *
 *				This will make Apache try other modules.
 *
 *				Defaults to on
 *
 * AuthGROUP_FallThrough on|off
 *
 *				If on, makes mod_auth_etc_group DECLINE if it can't
 *				find the username in the group as pulled from /etc/group
 *			  	or if it cannot find the group.
 *
 *				Please note that, if it DOES find the group, and
 *				the user is NOT in it; it will deny acces.
 *
 *				Defaults to off
 *
 */

#include <sys/types.h>
#include <pwd.h>		/* for getpwnam et.al. */
#include <grp.h>		/* for getpwnam et.al. */
#include <unistd.h>

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

#define VERSION "1.0a"

module etc_group_auth_module;

/*
 * configuration directive handling
 */

typedef struct {
  int
    fall_through, 	/* 1 to DECLINE instead of HTTP_UNAUTHORIZEDif we
			   can't find the username in a group. 
			   (default to 0) */
    enabled;		/* 1 to use mod_auth_etc_group, 0 otherwise
		   	  (defaults to 1) */
} auth_etc_group_dir_config;

static
int auth_etc_group_init(
	apr_pool_t *p,
	apr_pool_t *plog,
	apr_pool_t *ptemp,
	server_rec *s
)
{
  ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,"GROUP: mod_auth_etc_group/" VERSION);

  return OK;
}

static
void* create_auth_etc_group_dir_config(apr_pool_t *p, char *dummy)
{
  auth_etc_group_dir_config *new =
    (auth_etc_group_dir_config*) apr_palloc (p, sizeof(auth_etc_group_dir_config));

  new->fall_through   = 0; /* off */
  new->enabled	      = 1; /* on */
  return new;
}

static command_rec auth_etc_group_cmds[] = {

  AP_INIT_FLAG("AuthGROUP_FallThrough", 
	ap_set_flag_slot, 
     (void *)APR_OFFSETOF(auth_etc_group_dir_config, fall_through), 
	OR_AUTHCFG,
    "on|off - determines if other authentication methods are attempted "
    "if this one fails; (default is off.)"),

  AP_INIT_FLAG("AuthGROUP_Enabled", 
	ap_set_flag_slot, 
     (void *)APR_OFFSETOF(auth_etc_group_dir_config, enabled), 
	OR_AUTHCFG,
    "on|off - determines if GROUP authentication is enabled; ("
    "default is on.)" ),

  { NULL }
};

/*
 * These functions return 0 if client is OK, and proper error status
 * if not... either AUTH_REQUIRED, if we made a check, and it failed, or
 * SERVER_ERROR, if things are so totally confused that we couldn't
 * figure out how to tell if the client is authorized or not.
 *
 * If they return DECLINED, and all other modules also decline, that's
 * treated by the server core as a configuration error, logged and
 * reported as such.
 */



static
int etc_group_check_auth (request_rec *r)
{
  register int i = 0;
  char method_restricted = 0, *line = 0, *word = 0;
  auth_etc_group_dir_config *conf = 
	(auth_etc_group_dir_config*) ap_get_module_config(
		r->per_dir_config, &etc_group_auth_module);
  char *p_group = NULL;
  
  /* check for allowed users/group */
  const apr_array_header_t *reqs_arr = ap_requires (r);
  require_line *reqs = 0;

  /* enabled? */
  if (!conf->enabled)
    return DECLINED;

  /* if any valid user suffices return success */
  if (!reqs_arr)
    return (OK);

  /* otherwise */
  reqs = (require_line*)reqs_arr->elts;

  /*  loop over requirement lines */
  for( i = 0; i < reqs_arr->nelts; i++) {
    /* if method of this requirement matches current method */
    if (reqs[i].method_mask & (1 << r->method_number)) {
      method_restricted = 1;

      line = reqs[i].requirement;
      word = ap_getword(r->pool, (const char**)&line, ' ');

      if(!strcmp(word, "group") && (r->user))
      {
	struct group *grent;
	char **members;

	if (!p_group)
	{
		struct passwd *pwent;
		if ((pwent = getpwnam (r->user)) && (grent = getgrgid (pwent->pw_gid)))
				p_group = grent->gr_name;
	}

	while (*line)
	{
		word = ap_getword_conf(r->pool, (const char**)&line);
		if (p_group && !strcmp (p_group, word))
			return OK;

		if ((grent = getgrnam (word)) && grent->gr_mem)
		{
			members = grent->gr_mem;

			while (*members)
			{
				if (!strcmp (*members, word))
					return OK;

				members ++;
			}
		}
	}
      } /* end if group */
    } /* method mask */
  }

nope:

  if (!method_restricted)
    return OK;

  ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "GROUP: %s not in required group(s).",r->user);

  ap_note_basic_auth_failure (r);
  return HTTP_UNAUTHORIZED;
}

static void etc_group_register_hooks(apr_pool_t *p)
{  
    ap_hook_post_config(auth_etc_group_init, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_auth_checker(etc_group_check_auth,NULL,NULL,APR_HOOK_MIDDLE);

}   

module AP_MODULE_DECLARE_DATA etc_group_auth_module = {
  STANDARD20_MODULE_STUFF,
  create_auth_etc_group_dir_config,  /* dir config creater */
  NULL,                        /* dir merger --- default is to override */
  NULL,                        /* server config */
  NULL,                        /* merge server config */
  auth_etc_group_cmds,               /* command table */
  etc_group_register_hooks,  	       /* register hooks */
};

