
#include <stdio.h>

#include "mecha.h"

#include "htaccess_engine.h"

#include "htaccess_basic_auth.h"

#include "htaccess_parser.h"
#include "iauthentication_scheme.h"

#define HTACCESS_FILENAME   ".htaccess"

struct s_HtaccessEngine
{
    httpTtrans *trans;

    HtaccessParser *parser;

    IAuthenticationScheme *authScheme;

    dynTstring *requestAuthType;
    dynTstring *requestAuthData;

    char *htaccessFilename;
};

HtaccessEngine *
htaccess_engine_new (httpTtrans *t)
{
    HtaccessEngine *instance = (HtaccessEngine *) malloc (sizeof (HtaccessEngine));

    instance->trans = t;

    instance->parser = NULL;

    instance->requestAuthType = NULL;
    instance->requestAuthData = NULL;

    instance->htaccessFilename = NULL;

    instance->authScheme = NULL;

    return (instance);
}

void
htaccess_engine_destroy (void *ptr)
{
    HtaccessEngine *instance = (HtaccessEngine *) ptr;

    if (instance->parser != NULL)
    {
        htaccess_parser_destroy (instance->parser);
    }

    if (instance->requestAuthType != NULL)
    {
        dynFfree (instance->requestAuthType);
    }

    if (instance->requestAuthData != NULL)
    {
        dynFfree (instance->requestAuthData);
    }

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

    if (instance->authScheme != NULL)
    {
        instance->authScheme->destroy (instance->authScheme);
    }

    free (instance);
}

static bool
htaccess_engine_find_file (HtaccessEngine *instance)
{
    const lstTfield *path = lstFset_get_field (instance->trans->vars, "path", 4);
    char pathBuf[1024];
    char htfileBuf[1024];
    char *slashPtr;

    strncpy (pathBuf, lstFfield_value (path), sizeof (pathBuf));

    while ((slashPtr = rindex (pathBuf, '/')) != NULL)
    {
        struct stat sbuf;

        *slashPtr = 0;

        snprintf (htfileBuf, sizeof (htfileBuf), "%s/%s", pathBuf, HTACCESS_FILENAME);

        if (stat (htfileBuf, &sbuf) != -1)
        {
            if (S_ISREG (sbuf.st_mode))
            {
                instance->htaccessFilename = strFcopy (htfileBuf);

                instance->parser = htaccess_parser_new (htfileBuf);

                if (htaccess_parser_parse (instance->parser) == true)
                {
                    return (true);
                }
                else
                {
                    return (false);
                }
            }
        }
    }

    return (false);
}

static void
htaccess_engine_populate_auth_data (HtaccessEngine *instance)
{
    char *spacePtr;
    const lstTfield *auth = lstFset_get_field (instance->trans->req_hdrs,
            "authorization", 13);

    if (auth == NULL)
    {
        return;
    }

    spacePtr = index (lstFfield_value (auth), ' ');

    if (spacePtr == NULL)
    {
        return;
    }

    {
        int wordLen = spacePtr - lstFfield_value (auth);

        instance->requestAuthType = dynFinit();

        dynFappend (instance->requestAuthType, lstFfield_value (auth), wordLen);
    }

    instance->requestAuthData = dynFinit();

    spacePtr++;

    dynFsappend (instance->requestAuthData, spacePtr);
}

static int
htaccess_engine_send_unauthorized_response (HtaccessEngine *instance)
{
    char authResp[1024];
    const char *authRealm;
    const char *authType;

    httpFset_status (instance->trans, HTTP_UNAUTHORIZED, NULL);

    authRealm = htaccess_parser_get_value (instance->parser, "AuthName");
    authType = htaccess_parser_get_value (instance->parser, "AuthType");

    snprintf (authResp, sizeof (authResp), "%s realm=\"%s\"",
            authType, authRealm);

    lstFset_update (instance->trans->res_hdrs, "WWW-Authenticate", authResp);

    httpFstart_response (instance->trans);

    httpFwrite (instance->trans, "Authorization required.", 23);

    return (STATUS_EXIT);
}

static IAuthenticationScheme *
htaccess_engine_get_auth_scheme (HtaccessEngine *instance)
{
    if (strcmp (htaccess_parser_get_value (instance->parser, "AuthType"), "Basic") == 0)
    {
        return ((IAuthenticationScheme *) htaccess_basic_auth_new());
    }

    /* 
     * we only support the Basic authentication type right now because we're
     * lazy.
     *
     *  -eric@5stops.com 3.14.2004
     */
    return (NULL);
}

int
htaccess_engine_process (HtaccessEngine *instance)
{
    AuthenticateResult authResult = AuthenticateResultReject;
    const char *authType;

    if (htaccess_engine_find_file (instance) == false)
    {
        return (STATUS_PROCEED);
    }

    htaccess_engine_populate_auth_data (instance);

    authType = htaccess_parser_get_value (instance->parser, "AuthType");

    if (authType == NULL)
    {
        logFmsg (CONT_LOG_WARN, "mod/htaccess: Could not find an AuthType. Ignoring %s.",
                instance->htaccessFilename);

        return (STATUS_PROCEED);
    }

    if (instance->requestAuthType == NULL || instance->requestAuthData == NULL)
    {
        return (htaccess_engine_send_unauthorized_response (instance));
    }

    if (strcmp (authType, dynFgetstr (instance->requestAuthType)) != 0)
    {
        logFmsg (CONT_LOG_ERROR, "mod/htaccess: The requested authentication type \"%s\" did not match the expected type \"%s\".",
                dynFgetstr (instance->requestAuthType), authType);

        return (htaccess_engine_send_unauthorized_response (instance));
    }

    instance->authScheme = htaccess_engine_get_auth_scheme (instance);

    if (instance->authScheme == NULL)
    {
        logFmsg (CONT_LOG_ERROR, "mod/htaccess: %s: The authentication type \"%s\" is not supported.", 
                instance->htaccessFilename, authType);

        return (STATUS_PROCEED);
    }

    if (instance->authScheme->read_config (instance->authScheme, instance->parser) == false)
    {
        logFmsg (CONT_LOG_WARN, "mod/htaccess: %s: An error occured while reading config data.",
                instance->htaccessFilename);

        return (STATUS_PROCEED);
    }

    authResult = instance->authScheme->auth_user (instance->authScheme, instance->trans, 
            dynFgetstr (instance->requestAuthData));

    if (authResult == AuthenticateResultReject)
    {
        return (htaccess_engine_send_unauthorized_response (instance));
    }

    return (STATUS_PROCEED);
}
