/***********************************************************************
 * 
 *  Copyright (C) 2006 Novell, Inc. All Rights Reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; version 2.1
 *  of the License.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Library Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, Novell, Inc.
 * 
 *  To contact Novell about this file by physical or electronic mail, 
 *  you may find current contact information at www.novell.com.
 * 
 *  Author: Juan Carlos Luciani <jluciani@novell.com>
 *
 ***********************************************************************/

#include <stdbool.h>

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

#include "mod_auth.h"

#include "casa_s_authtoken.h"

//
// Module per-dir configuration structure.
// 
typedef struct _authn_casa_dir_cfg
{
   int   performUsernameCheck;

} authn_casa_dir_cfg;


//
// Forward declaration of our module structure.
// 
module AP_MODULE_DECLARE_DATA authn_casa_module;

//
// Environment variables set by module
// 
static char CasaIdentityIdEnvVar[] = "IdentityId";
static char CasaIdentitySourceNameEnvVar[] = "IdentityDataSourceName";
static char CasaIdentitySourceUrlEnvVar[] = "IdentityDataSourceUrl";

//
// Function: create_per_dir_config()
// 
// Create per-dir configuration structure.
// 
static void*
create_per_dir_config(
   apr_pool_t *p,
   char *x)
{
    authn_casa_dir_cfg *pDirConfig;

    // Allocate space for our configuration structure
    pDirConfig = (authn_casa_dir_cfg*) apr_palloc(p, sizeof(*pDirConfig));

    // Return our new configuration structure
    return  (void*) pDirConfig;
}

/* ************************************************************************
 * set_authn_casa_uname_check()
 * 
 * Process UsernameCheck configuration directive..
 *
 * L2
 * ************************************************************************/
static const char*
set_authn_casa_uname_check(
   cmd_parms *cmd,
   void *cfg,
   int arg)
{
   authn_casa_dir_cfg *pDirConfig = (authn_casa_dir_cfg*) cfg;

   // Record the value in our structure
   pDirConfig->performUsernameCheck = arg;

   return NULL;
}

//
// Configuration directives array structure.
// 
static const command_rec authn_casa_cmds[] =
{
    AP_INIT_FLAG("UsernameCheck",               // tbd - May be this directive should be on a per-directory or per-location basis
                 set_authn_casa_uname_check,
                 NULL,
                 OR_AUTHCFG,
                 "Check for username == CasaPrincipal (Value limited to 'on' or 'off')"),
    {NULL}
};

/* ************************************************************************
 * check_password()
 * 
 * Given a user and password, expected to return AUTH_GRANTED if we
 * can validate the user/password combination.
 *
 * L2
 * ************************************************************************/
static authn_status
check_password(
   request_rec *r,
   const char *user,
   const char *password)
{
   authn_status         retStatus; 
   authn_casa_dir_cfg   *pDirConfig;
   bool                 userNameChecked = false;
   int                  i;
   char                 *pLocationName;

   // First determine the length of the name of the location being protected
   i = 0;
   while (r->uri[i] != '\0')
   {
      if (r->uri[i] == '/')
      {
         // Ignore the slash if it is at the beginning of the uri
         if (i != 0)
         {
            // The slash is not at the beggining of the uri, stop.
            break;
         }
      }

      i++;
   }

   // Now get a copy of the location being protected
   if (i > 1)
   {
      pLocationName = apr_palloc(r->pool, i + 1);
      if (pLocationName)
      {
         memset(pLocationName, 0, i + 1);
         memcpy(pLocationName, &(r->uri[1]), i - 1);  // Do not include the slashes
      }
      else
      {
         ap_log_rerror(APLOG_MARK, APLOG_ALERT, 0, r, "Memory allocation failure");
         return AUTH_GENERAL_ERROR;
      }
   }
   else
   {
      // We are protecting the server root
      pLocationName = "apache_root";
   }

   // Get access to our per-dir configuration structure
   pDirConfig = ap_get_module_config(r->per_dir_config,
                                     &authn_casa_module);
   if (pDirConfig)
   {
      // Assume success
      retStatus = AUTH_GRANTED;

      // Check if we must perform the username check
      if (pDirConfig->performUsernameCheck != 0)
      {
         // Remember that we performed this check
         userNameChecked = true;

         // Check if the username matches the name what we are expecting
         if (strcmp(user, "CasaPrincipal") != 0)
         {
            // The username does not match, allow other providers to get
            // a crack to it.
            retStatus = AUTH_USER_NOT_FOUND;
         }
      }

      // Check the token if a problem has not been found
      if (retStatus == AUTH_GRANTED)
      {
         CasaStatus  casaStatus;
         PrincipalIf *pPrincipalIf;

         // Validate the token
         casaStatus = ValidateAuthToken(pLocationName,
                                        password,
                                        strlen(password),
                                        &pPrincipalIf);
         if (CASA_SUCCESS(casaStatus))
         {
            size_t         buffLen = 0;
            apr_table_t    *e = r->subprocess_env;

            // Associate necessary environment variables with the request block
            casaStatus = pPrincipalIf->getIdentityId(pPrincipalIf,
                                                     NULL,
                                                     &buffLen);
            if (CasaStatusCode(casaStatus) == CASA_STATUS_BUFFER_OVERFLOW)
            {
               char  *pBuff;

               // Allocate buffer to obtain the Identity Id
               pBuff = apr_pcalloc(r->pool, buffLen);
               if (pBuff)
               {
                  // Read the value into our buffer
                  if (CASA_SUCCESS(pPrincipalIf->getIdentityId(pPrincipalIf,
                                                               pBuff,
                                                               &buffLen)))
                  {
                     // Now set the environment variable
                     apr_table_setn(e, CasaIdentityIdEnvVar, pBuff);

                     // Also, update the username within the request block with the identity id
                     r->user = pBuff;
                  }
                  else
                  {
                     ap_log_rerror(APLOG_MARK, APLOG_ALERT, 0, r, "Unable to obtain identity id");
                     retStatus = AUTH_GENERAL_ERROR;
                  }
               }
               else
               {
                  ap_log_rerror(APLOG_MARK, APLOG_ALERT, 0, r, "Memory allocation failure");
                  retStatus = AUTH_GENERAL_ERROR;
               }
            }
            else
            {
               ap_log_rerror(APLOG_MARK, APLOG_ALERT, 0, r, "Un-expected error obtaining identity id, %0X", casaStatus);
               retStatus = AUTH_GENERAL_ERROR;
            }

            if (retStatus == AUTH_GRANTED)
            {
               buffLen = 0;
               casaStatus = pPrincipalIf->getSourceName(pPrincipalIf,
                                                        NULL,
                                                        &buffLen);
               if (CasaStatusCode(casaStatus) == CASA_STATUS_BUFFER_OVERFLOW)
               {
                  char  *pBuff;

                  // Allocate buffer to obtain the Identity Source Name
                  pBuff = apr_pcalloc(r->pool, buffLen);
                  if (pBuff)
                  {
                     // Read the value into our buffer
                     if (CASA_SUCCESS(pPrincipalIf->getSourceName(pPrincipalIf,
                                                                  pBuff,
                                                                  &buffLen)))
                     {
                        // Now set the environment variable
                        apr_table_setn(e, CasaIdentitySourceNameEnvVar, pBuff);
                     }
                     else
                     {
                        ap_log_rerror(APLOG_MARK, APLOG_ALERT, 0, r, "Unable to obtain identity source name");
                        retStatus = AUTH_GENERAL_ERROR;
                     }
                  }
                  else
                  {
                     ap_log_rerror(APLOG_MARK, APLOG_ALERT, 0, r, "Memory allocation failure");
                     retStatus = AUTH_GENERAL_ERROR;
                  }
               }
               else
               {
                  ap_log_rerror(APLOG_MARK, APLOG_ALERT, 0, r, "Un-expected error obtaining identity source name, %0X", casaStatus);
                  retStatus = AUTH_GENERAL_ERROR;
               }
            }

            if (retStatus == AUTH_GRANTED)
            {
               buffLen = 0;
               casaStatus = pPrincipalIf->getSourceUrl(pPrincipalIf,
                                                       NULL,
                                                       &buffLen);
               if (CasaStatusCode(casaStatus) == CASA_STATUS_BUFFER_OVERFLOW)
               {
                  char  *pBuff;

                  // Allocate buffer to obtain the Identity Source Url
                  pBuff = apr_pcalloc(r->pool, buffLen);
                  if (pBuff)
                  {
                     // Read the value into our buffer
                     if (CASA_SUCCESS(pPrincipalIf->getSourceUrl(pPrincipalIf,
                                                                 pBuff,
                                                                 &buffLen)))
                     {
                        // Now set the environment variable
                        apr_table_setn(e, CasaIdentitySourceUrlEnvVar, pBuff);
                     }
                     else
                     {
                        ap_log_rerror(APLOG_MARK, APLOG_ALERT, 0, r, "Unable to obtain identity source url");
                        retStatus = AUTH_GENERAL_ERROR;
                     }
                  }
                  else
                  {
                     ap_log_rerror(APLOG_MARK, APLOG_ALERT, 0, r, "Memory allocation failure");
                     retStatus = AUTH_GENERAL_ERROR;
                  }
               }
               else
               {
                  ap_log_rerror(APLOG_MARK, APLOG_ALERT, 0, r, "Un-expected error obtaining identity source url, %0X", casaStatus);
                  retStatus = AUTH_GENERAL_ERROR;
               }
            }

            if (retStatus == AUTH_GRANTED)
            {
               char           *pAttribNameBuff, *pAttribValueBuff;
               unsigned int   enumHandle = 0;
               size_t         attribNameBuffLen, attribValueBuffLen;

               while (retStatus == AUTH_GRANTED)
               {
                  // Get attribute lengths
                  attribNameBuffLen = attribValueBuffLen = 0;
                  casaStatus = pPrincipalIf->attributeEnumerate(pPrincipalIf,
                                                                &enumHandle,
                                                                NULL,
                                                                &attribNameBuffLen,
                                                                NULL,
                                                                &attribValueBuffLen);
                  if (CasaStatusCode(casaStatus) == CASA_STATUS_BUFFER_OVERFLOW)
                  {
                     // Allocate buffers to obtain the attribute data
                     pAttribNameBuff = apr_pcalloc(r->pool, attribNameBuffLen);
                     pAttribValueBuff = apr_pcalloc(r->pool, attribValueBuffLen);
                     if (pAttribNameBuff && pAttribValueBuff)
                     {
                        // Read the attribute into our buffer
                        if (CASA_SUCCESS(pPrincipalIf->attributeEnumerate(pPrincipalIf,
                                                                          &enumHandle,
                                                                          pAttribNameBuff,
                                                                          &attribNameBuffLen,
                                                                          pAttribValueBuff,
                                                                          &attribValueBuffLen)))
                        {
                           // Now set the environment variable
                           apr_table_setn(e, pAttribNameBuff, pAttribValueBuff);
                        }
                        else
                        {
                           ap_log_rerror(APLOG_MARK, APLOG_ALERT, 0, r, "Unable to obtain identity attribute");
                           retStatus = AUTH_GENERAL_ERROR;
                        }
                     }
                     else
                     {
                        ap_log_rerror(APLOG_MARK, APLOG_ALERT, 0, r, "Memory allocation failure");
                        retStatus = AUTH_GENERAL_ERROR;
                     }
                  }
                  else
                  {
                     // Check if we are done going through the attributes
                     if (CasaStatusCode(casaStatus) == CASA_STATUS_NO_MORE_ENTRIES)
                     {
                        // Done
                        break;
                     }
                     else
                     {
                        ap_log_rerror(APLOG_MARK, APLOG_ALERT, 0, r, "Un-expected error during attribute enumeration, %0X", casaStatus);
                        retStatus = AUTH_GENERAL_ERROR;
                     }
                  }
               }
            }

            // Release the principal interface instance
            pPrincipalIf->releaseReference(pPrincipalIf);
         }
         else
         {
            // Check if the token validation failed for a CasaPrincipal
            if (userNameChecked)
            {
               // Token validation failed for a CasaPrincipal, always return AUTH_DENIED.
               retStatus = AUTH_DENIED;
            }
            else
            {
               // We did not check the username, allow other providers to get a crack to it.
               retStatus = AUTH_USER_NOT_FOUND;
            }
         }
      }
   }
   else
   {
      ap_log_rerror(APLOG_MARK, APLOG_ALERT, 0, r, "Did not get module per-server config structure");
      retStatus = AUTH_GENERAL_ERROR;
   }

    return retStatus;
}

//
// Authentication Provider Function Table
// 
static const authn_provider authn_casa_provider =
{
    &check_password,
    NULL,            // We do not support Digest Authentication
};

/* ************************************************************************
 * register_hooks()
 * 
 * Register all of the module hooks.
 *
 * L2
 * ************************************************************************/
static void
register_hooks(
   apr_pool_t *p)
{
   // Register as an authentication provider
   ap_register_provider(p,
                        AUTHN_PROVIDER_GROUP,   // Provider group
                        "casa",                 // Provider name
                        "0",                    // Provider version
                        &authn_casa_provider);  // Authentication Provider function table
}

//
// Declare ourselves to the HTTPD core.
// 
module AP_MODULE_DECLARE_DATA authn_casa_module =
{
   STANDARD20_MODULE_STUFF,
   create_per_dir_config,        // Create per-dir config structures
   NULL,                         // merge per-dir config structures
   NULL,                         // Create per-server config structures
   NULL,                         // merge per-server config structures
   authn_casa_cmds,              // command handlers
   register_hooks                // register hooks
};