
#include <stdio.h>

#include "htaccess_basic_auth.h"

#include "iauthentication_scheme.h"

#include "mecha.h"
#include "http.h"

#ifdef SOLARIS
#include <crypt.h>
#endif

enum RequireType_e
{
    RequireTypeValidUser,
    RequireTypeUser,
    RequireTypeGroup,
    RequireTypeNone
};

typedef enum RequireType_e RequireType;

struct s_HtaccessBasicAuth
{
    IAuthenticationScheme authInterface;

    const char *realm;
    const char *userFile;
    const char *groupFile;
    RequireType requireType;

    char *requestedUser;
    char *requestedPassword;
};


static bool htaccess_basic_auth_read_config (IAuthenticationScheme *inst, HtaccessParser *config);
static AuthenticateResult htaccess_basic_auth_auth_user (IAuthenticationScheme *inst, httpTtrans *trans, const char *data);
static bool htaccess_basic_auth_parse_data (HtaccessBasicAuth *instance, const char *data);

HtaccessBasicAuth *
htaccess_basic_auth_new (void)
{
    HtaccessBasicAuth *instance = (HtaccessBasicAuth *) malloc (sizeof (HtaccessBasicAuth));

    instance->authInterface.read_config = htaccess_basic_auth_read_config;
    instance->authInterface.auth_user = htaccess_basic_auth_auth_user;
    instance->authInterface.destroy = (IAuthenticationSchemeDestroy) htaccess_basic_auth_destroy;

    instance->realm = NULL;
    instance->userFile = NULL;
    instance->groupFile = NULL;
    instance->requireType = RequireTypeNone;

    instance->requestedUser = NULL;
    instance->requestedPassword = NULL;

    return (instance);
}

void
htaccess_basic_auth_destroy (void *ptr)
{
    HtaccessBasicAuth *instance = (HtaccessBasicAuth *) ptr;

    if (instance->requestedUser != NULL)
    {
        free (instance->requestedUser);
    }
    
    if (instance->requestedPassword != NULL)
    {
        free (instance->requestedPassword);
    }

    free (instance);
}

static bool
htaccess_basic_auth_read_config (IAuthenticationScheme *inst, HtaccessParser *config)
{
    HtaccessBasicAuth *instance = (HtaccessBasicAuth *) inst;
    const char *requireField;

    requireField = htaccess_parser_get_value (config, "Require");

    if (requireField == NULL)
    {
        instance->requireType = RequireTypeNone;
    }
    else if (strncmp (requireField, "valid-user", 10) == 0)
    {
        instance->requireType = RequireTypeValidUser;
    }
    else if (strncmp (requireField, "user ", 5) == 0)
    {
        instance->requireType = RequireTypeUser;

        logFmsg (CONT_LOG_ERROR, "mod/htaccess: 'Require user' unsupported.");

        return (false);
    }
    else if (strncmp (requireField, "group ", 5) == 0)
    {
        instance->requireType = RequireTypeGroup;

        logFmsg (CONT_LOG_ERROR, "mod/htaccess: 'Require group' unsupported.");

        return (false);
    }

    instance->userFile = htaccess_parser_get_value (config, "AuthUserFile");
    instance->groupFile = htaccess_parser_get_value (config, "AuthGroupFile");

    instance->realm = htaccess_parser_get_value (config, "AuthName");

    return (true);
}

static bool
htaccess_basic_auth_parse_data (HtaccessBasicAuth *instance, const char *data)
{
    char decodeBuf[1024];
    char *user, *pass;
    char *st_p;
    int ret;

    ret = b64Fdecode (data, (unsigned char *) decodeBuf, sizeof (decodeBuf));

    decodeBuf[ret] = 0;

    user = strtok_r (decodeBuf, ":", &st_p);
    pass = strtok_r (NULL, ":", &st_p);

    if (user != NULL && pass != NULL)
    {
        instance->requestedUser = strFcopy (user);
        instance->requestedPassword = strFcopy (pass);

        return (true);
    }
    else
    {
        return (false);
    }
}

static AuthenticateResult
htaccess_basic_auth_auth_user (IAuthenticationScheme *inst, 
        httpTtrans *trans, const char *data)
{
    HtaccessBasicAuth *instance = (HtaccessBasicAuth *) inst;
    FILE *fp;
    char line[1024];
    bool retSuccess = false;

    if (htaccess_basic_auth_parse_data (instance, data) == false)
    {
        if (instance->requireType == RequireTypeNone)
        {
            return (AuthenticateResultAccept);
        }
        else
        {
            return (AuthenticateResultReject);
        }
    }

    fp = fopen (instance->userFile, "r");

    if (fp == NULL)
    {
        if (instance->requireType == RequireTypeNone)
        {
            logFmsg (CONT_LOG_WARN, "mod/htaccess: Could not open UserFile: %s. Authentication succeeding.",
                    instance->userFile);

            return (AuthenticateResultAccept);
        }
        else
        {
            logFmsg (CONT_LOG_WARN, "mod/htaccess: Could not open UserFile: %s. Authentication failing.",
                    instance->userFile);

            return (AuthenticateResultReject);
        }
    }

    while (fgets (line, sizeof (line), fp) != NULL)
    {
        char *tokCtx;
        char *curUser, *curPassword;

        strFstrip_crlf (line, strlen (line));

        curUser = strtok_r (line, ":", &tokCtx);

        curPassword = strtok_r (NULL, ":", &tokCtx);

        if (curPassword == NULL)
        {
            continue;
        }

        /* if we found the user */
        if (strcmp (instance->requestedUser, curUser) == 0)
        {
            char *encryptedPass;

            encryptedPass = crypt (instance->requestedPassword, curPassword);

            if (strcmp (encryptedPass, curPassword) == 0)
            {
                retSuccess = true;
            }
            else
            {
#if HTACCESS_DEBUG
                logFmsg (CONT_LOG_DEBUG, "mod/htaccess: File password %s does not match encrypted: %s", 
                        curPassword, encryptedPass);
#endif

                retSuccess = false;
            }

            break;
        }
    }

    fclose (fp);

    if (retSuccess == true)
    {
        lstFset_update (trans->vars, "auth-type", "basic");
        lstFset_update (trans->vars, "auth-user", instance->requestedUser);

        return (AuthenticateResultAccept);
    }
    else
    {
        if (instance->requireType == RequireTypeNone)
        {
            return (AuthenticateResultAccept);
        }
        else
        {
            return (AuthenticateResultReject);
        }
    }
}

