/*
 * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

/*
 *  PROPRIETARY/CONFIDENTIAL.  Use of this product is subject to license terms.
 *  Copyright  1999 Sun Microsystems, Inc. Some preexisting portions Copyright
 *  1999 Netscape Communications Corp. All rights reserved.
 */

/*
 * auth_dbm.c: Sample htaccess-based NSAPI authentication module using
 *             Netscape DBM file format.
 *
 *
 * Based on work by Rob McCool & Brian Behlendorf.
 */

#ifdef XP_WIN32
#define NSAPI_PUBLIC __declspec(dllexport)
#else /* !XP_WIN32 */
#define NSAPI_PUBLIC
#endif /* !XP_WIN32 */

/* The following is the standard header for SAFs.  It is used to
 * get the data structures and prototypes needed to declare and use SAFs.
 */

#include "nsapi.h"
#include "htmodule.h"
#include <fcntl.h>

#include <ndbm.h>

#ifdef XP_WIN32
char * crypt(const char *, const char *);
#endif

static CRITICAL auth_crit = NULL;

/* ------------------ htaccess DBM Authentication Module ----------------- 

   Compiling:
      Be sure that ndbm.h is in the include search path (-I).

      In order to create/manage Netscape DBM databases you need
      to link your programs with the built-in version of DBM at
      [server-root]/bin/https/lib/liblibdbm.so

      So add something like: -L[server-root]/bin/https/lib -llibdbm
      where [server-root] points to where you installed the server.

   Usage:

      In order for this plug-in to work you need to have htaccess
      enabled and these directives must come FIRST in init.conf.
      This will consist of an Init load-modules statement for htaccess.so
      and Init fn="htaccess-init"
  
      To enable the DBM plugin, add this to the end of init.conf:
         Init fn=load-modules shlib=auth_dbm.<ext> \
         funcs="set-dbmuserfile,set-dbmgrpfile,dbm-module-init,dbm-check-auth,dbm-check-group"

         Init fn=dbm-module-init

   <ext> = so on UNIX
   <ext> = dll on NT.

   The shared library must either be in the LD_LIBRARY_PATH of the server
   or the path to the file must be included with auth_dbm.[so|dll]

   Here is a list of possible directives:

    AuthDBMUserFile /usr/netscape/docs/htpasswd

                    The DBM database used for authenticating users.  The
                    file has the format:

                       key=username value=password":"[group1,group2,...,groupn]

    AuthDBMGroupFile /usr/netscape/docs/htpasswd

                    The DBM database for checking group access.  If it is
                    the same file as AuthDBMUserFile then it needs to take
                    the above format.  If not, the it needs to be in the
                    format:

                        key=username value=group1,group2,...,groupn

    AuthDBMAuthoritative on/off

                     Silently ignored by the server.
 */

/* ---------------------- Start DBM-specific code ------------------------ */

static char * get_entry(char * item, char * pwfile, Session *sn, Request *rq)
{
    DBM *db;
    datum data, key;
    char *dbmpw = NULL;

    key.dptr = item;
    key.dsize = strlen(key.dptr) + 1;

    if (!(db = dbm_open(pwfile, O_RDWR, 0660))) {
        log_error(LOG_FAILURE, "dbm-check-auth", sn, rq, 
            "could not open DBM file: %s", pwfile);
        protocol_status(sn, rq, PROTOCOL_SERVER_ERROR, NULL);
        return NULL;
    }

    log_error(LOG_VERBOSE, "dbm-check-auth", sn, rq, "Authenticating %s", key.dptr);

    data = dbm_fetch(db, key);

    if (data.dptr) {
        dbmpw = STRDUP(data.dptr);
    }
    else { 
       log_error(LOG_VERBOSE, "dbm-check-auth", sn, rq, "Entry %s not found", key.dptr);
       dbmpw = NULL;
    }

    dbm_close(db);

    return(dbmpw);
}

/* Get the user's password from the DBM file and check to see if it is
 * correct.  Only supports crypt() passwords.
 */
#ifdef __cplusplus
extern "C"
#endif
NSAPI_PUBLIC int dbm_check_auth(pblock *param, Session *sn, Request *rq)
{
    char *pwfile = pblock_findval("AuthDBMUserFile", rq->vars);
    char *user = pblock_findval("user", param);
    char *pw = pblock_findval("pw", param);

    char * password, * colon_pw;

    int rv = 0;

    if (!pwfile) {
        log_error(LOG_FAILURE, "dbm-check-auth", sn, rq, "Password file not specified.");
        return REQ_NOACTION;
    }

    if (!(password = get_entry(user, pwfile, sn, rq))) {
        log_error(LOG_SECURITY, "dbm-check-auth", sn, rq,
              "user %s does not exist in file %s",
              user, pwfile);
        return REQ_NOACTION;
    }

    /* Password is up to first : if exists */
    colon_pw = strchr(password, ':');
    if (colon_pw) {
        *colon_pw = '\0';
    }

    /* Ensure that we are thread-safe */
    crit_enter(auth_crit);
    rv = strcmp(password, (char *)crypt((char *)pw,(char *)password));
    crit_exit(auth_crit);

    if (rv != 0) {
        log_error(LOG_SECURITY, "dbm-check-auth", sn, rq,
              "user %s password does not match password in file %s",
              user, pwfile);
        rv = REQ_NOACTION;
    } else
 
        rv = REQ_PROCEED;

    if (password) FREE(password);

    return rv;
}

/* There are 2 possible formats for the group file:
 *
 *   1. key=username value=password":"group1,group2,...,groupn[":" extra data]
 *       anything past the 2nd colon is discared by the server
 *
 *   2. key=username value=group1,group2,...,groupn
 *
 * The first format allows the same DBM database to be used for both user
 * and group authentication.  If this is used then you can store the user
 * password before the first colon and the groups after it.
 */

#ifdef __cplusplus
extern "C"
#endif
NSAPI_PUBLIC int dbm_check_group(pblock *param, Session *sn, Request *rq)
{
    char *pwfile = pblock_findval("AuthDBMGroupFile", rq->vars);
    char *user = pblock_findval("user", param);
    char *group = pblock_findval("group", param);
    char *group_colon;
    char *group_colon2;
    char *groups_entry;
    char *groups, *x;

    int rv = 0;

    if (!(groups_entry = get_entry(user, pwfile, sn, rq))) {
        log_error(LOG_SECURITY, "dbm-check-auth", sn, rq,
              "group %s does not exist in file %s",
              group, pwfile);
        return REQ_NOACTION;
    }
    groups = groups_entry;
    
    /* Determine which format is being used */
    if ((group_colon = strchr(groups, ':')) != NULL) {
        group_colon2 = strchr(++group_colon, ':');
        if (group_colon2)
            *group_colon2 = '\0';
        groups = group_colon;
    }

    x = strtok(groups, ",");
    while (x) {
        if (!strcmp(group, x)) {
            FREE(groups_entry);
            return REQ_PROCEED;
        }
        x = strtok(NULL, ",");
       
    }
    FREE(groups_entry);

    return REQ_NOACTION;
}

/* Set the user database variable in the Request variables */
#ifdef __cplusplus
extern "C"
#endif
NSAPI_PUBLIC int set_dbmuserfile(pblock *param, Session *sn, Request *rq)
{
    char * pwfile = pblock_findval("arg1", param);
    pblock_nvinsert("AuthDBMUserFile", pwfile, rq->vars);

    return REQ_PROCEED;
}

/* Set the group database variable in the Request variables */
#ifdef __cplusplus
extern "C"
#endif
NSAPI_PUBLIC int set_dbmgrpfile(pblock *param, Session *sn, Request *rq)
{
    char * pwfile = pblock_findval("arg1", param);
    pblock_nvinsert("AuthDBMGroupFile", pwfile, rq->vars);

    return REQ_PROCEED;
}

/* ---------------------- End DBM-specific code ------------------------ */

/* ---------------- Start generic htaccess module code ------------------*/

/* This function should be included in any htaccess-based authentication
 * module.  It handles registering the directive with the main htaccess
 * plug-in.  This is done via a new pblock that is recognized by the
 * htaccess plug-in.
 */
static int
register_directive(char * directive, char * function, int argtype, int authtype,
         char * check_fn, Session *sn, Request *rq, NSAPI_PUBLIC int fn)
{
    Request * nrq = rq;
    FuncPtr pFunc = NULL;
    pblock *npb;
    int rv;
    char a[15];

    /* Insert the variables into the pblock */
    npb = pblock_create(6);
    pblock_nvinsert("fn", "htaccess-register", npb);
    pblock_nvinsert("directive", directive, npb);
    util_itoa(argtype, a);
    pblock_nvinsert("argtype", a, npb);
    util_itoa(authtype, a);
    pblock_nvinsert("authtype", a, npb);
    pblock_nvinsert("function", function, npb);
    pblock_nvinsert("check_fn", check_fn, npb);
    
    if ((pFunc = func_find("htaccess-register")) == NULL)
    {
        log_error(LOG_FAILURE, "module", sn, rq, "htaccess-register() not found.");
        return(0);
    } else
        rv = func_exec(npb, sn, nrq);

    pblock_free(npb);
    func_insert(function, (FuncPtr) fn);

    if (rv !=  REQ_PROCEED)
        return (0);

    return(1);
}

/* Free up the thread-lock when the server exits. */
#ifdef __cplusplus
extern "C"
#endif
NSAPI_PUBLIC void cleanup(void *parameter)
{
    if (auth_crit)
        crit_terminate(auth_crit);
}

/* This function needs to be called before the htaccess module can be used.
 * This handles registering each directive, the number of arguments it takes
 * and its underlying function with the main htaccess plug-in.
 *
 */
#ifdef __cplusplus
extern "C"
#endif
NSAPI_PUBLIC int dbm_module_init(pblock *param, Session *sn, Request *rq)
{
    auth_crit = crit_init();

    log_error(LOG_VERBOSE, "auth_dbm", sn, rq, "Initializing htaccess DBM module.");
    if (auth_crit == 0) {
        log_error(LOG_FAILURE, "auth_dbm", sn, rq, "Unable to obtain critical lock.");
        return REQ_ABORTED;
    }

    daemon_atrestart(cleanup, NULL);

    /* Register the two supported directives */
    if (!(register_directive("AuthDBMUserFile", "set-dbmuserfile", PASS1, USER_AUTH, "dbm-check-auth", sn, rq, (NSAPI_PUBLIC int) set_dbmuserfile))) {
        return REQ_ABORTED;
    }
    if (!(register_directive("AuthDBMGroupFile", "set-dbmgrpfile", PASS1, GROUP_AUTH, "dbm-check-group", sn, rq, (NSAPI_PUBLIC int) set_dbmgrpfile))) {
        return REQ_ABORTED;
    }

    return REQ_PROCEED;
}
