/*  File: arealdap.c  */

/*****************************************************************************/
/*                                                                           */
/*                                                                           */
/*   ©Copyright  2009 – 2012 BMC Software, Inc.                              */
/*   BMC, BMC Software, the BMC logos and other BMC marks are trademarks or  */
/*   registered trademarks of BMC Software, Inc. in the U.S. and /or         */
/*   certain other countries.                                                */
/*****************************************************************************/

#ifdef _WIN32
#include <windows.h>
#endif /* _WIN32 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <ldap.h>

#include "area.h"

/*****************************************************************************/
/*                                                                           */
/*       Action Request System External Authentication (AREA) Sample         */
/*                                                                           */
/*****************************************************************************/
/* Description: This is a sample external authentication server based on     */
/*    LDAP.                                                                  */
/*                                                                           */
/*    This example uses the Netscape LDAP SDK available from Netscape        */
/*    Communications Corporation.  Schema and attribute names reflect those  */
/*    commonly found on a Netscape Directory Server.  Your own configuration */
/*    may differ.                                                            */
/*                                                                           */
/*    Possible enhancements to this example:                                 */
/*                                                                           */
/*    +  Cache user and/or group membership information so that we do not    */
/*       have to go to the LDAP server for every request.                    */
/*                                                                           */
/*    +  and/or, be smarter about when to flush the AR Server's cache of user*/
/*       user information.                                                   */
/*                                                                           */
/*    +  Generalize to work with other directory configurations and schemas. */
/*                                                                           */
/*****************************************************************************/

/*
 * Site specific defines
 */
#define MY_PORT         LDAP_PORT
#define MY_HOST         "localhost"
#define MY_PEOPLEBASE   "ou=People, o=Peregrine.COM"
#define MY_GROUPBASE    "ou=Groups, o=Peregrine.COM"


/*****************************************************************************/
/*                                                                           */
/*                             ARPluginIdentify                              */
/*                                                                           */
/*****************************************************************************/
/* Description: This function is called by the plug-in service to identify   */
/*    what type of plug-in this implements.                                  */
/*                                                                           */
/*****************************************************************************/

ARPLUGIN_EXPORT int ARPluginIdentify(
ARPluginIdentification *id, /* OUT; AR_PLUGIN_TYPE_...*/
ARStatusList *status        /* OUT; error message(s)*/
)
{
   id->type = AR_PLUGIN_TYPE_AREA;
   strcpy(id->name, "EXAMPLE.AREA.LDAP");
   id->version = AREA_PLUGIN_VERSION;
   memset(status, 0, sizeof(*status));

   return AR_RETURN_OK;
}


/*****************************************************************************/
/*                                                                           */
/*                          ARPluginInitialization                           */
/*                                                                           */
/*****************************************************************************/
/* Description: This function is called by the plug-in service to initilize  */
/*    the plug-in.  This is where the plug-in should initialize any data     */
/*    that will be global and accessed by all instances of the plug-in that  */
/*    are created via ARPluginCreateInstance().                              */
/*                                                                           */
/*****************************************************************************/

ARPLUGIN_EXPORT int ARPluginInitialization(
int argc,             /*  IN; argc from main() */
char **argv,          /*  IN; argv from main() */
ARStatusList *status
)
{
   if (status != NULL)
      memset(status, 0, sizeof(*status));

   return AR_RETURN_OK;
}


/*****************************************************************************/
/*                                                                           */
/*                          ARPluginTermination                              */
/*                                                                           */
/*****************************************************************************/
/* Description: This function is called by the plug-in service to terminate  */
/*    the plug-in.  This is where the plug-in should deallocate any          */
/*    resources that have been allocated either globally or by each instance */
/*    created by ARPluginCreateInstance().                                   */
/*                                                                           */
/*****************************************************************************/

ARPLUGIN_EXPORT int ARPluginTermination(
ARStatusList *status
)
{
   if (status != NULL)
      memset(status, 0, sizeof(*status));
   
   return AR_RETURN_OK;
}


/*****************************************************************************/
/*                                                                           */
/*                          ARPluginCreateInstance                           */
/*                                                                           */
/*****************************************************************************/
/* Description: This function is called by the plug-in service to create an  */
/*    instance of the plug-in.  Each instance is guaranteed to be involved   */
/*    in one thread of operation at any one time.                            */
/*                                                                           */
/*    The structure returned via the 'object' pointer will be provided in    */
/*    subsequent plug-in calls.  This allows you to attach arbitrary data    */
/*    to an instance that will be inherently thread safe.                    */
/*                                                                           */
/*****************************************************************************/

ARPLUGIN_EXPORT int ARPluginCreateInstance(
void **object,
ARStatusList *status
)
{
   if (status != NULL)
      memset(status, 0, sizeof(*status));

   if (object != NULL)
   {
      /*
       * Set *object to point to something that you have 
       * allocated and initialized.
       */
      *object = NULL;
   }

   return AR_RETURN_OK;
}


/*****************************************************************************/
/*                                                                           */
/*                          ARPluginDeleteInstance                           */
/*                                                                           */
/*****************************************************************************/
/* Description: This function is called by the plug-in service to delete an  */
/*    instance of the plug-in.  Each instance is guaranteed to be involved   */
/*    in one thread of operation at any one time.                            */
/*                                                                           */
/*****************************************************************************/

ARPLUGIN_EXPORT int ARPluginDeleteInstance(
void *object,
ARStatusList *status
)
{
   if (object != NULL)
   {
      /*
       * Anything that you allocated in ARPluginCreateInstance
       * should be deallocated here.
       */
   }

   return AR_RETURN_OK;
}


/*****************************************************************************/
/*                                                                           */
/*                          AREAVerifyLoginCallback                          */
/*                                                                           */
/*****************************************************************************/
/* Description: This function is called by the AREA core library whenever a  */
/*    request is made by the AR System Server to authenticate a user.        */
/*                                                                           */
/*    The basic idea is to authenticate (also known as "binding") with the   */
/*    the LDAP server.  If this is successful, we go on to get other         */
/*    information such as e-mail address and group membership.               */
/*                                                                           */
/*****************************************************************************/

void AREAVerifyLoginCallback(
void                *object,
ARAccessNameType     user,
ARPasswordType	     password,
ARNameType	     networkAddr,
ARAuthType           authString,
AREAResponseStruct **response
)
{
   LDAP         *ld;
   LDAPMessage  *result, *e;
   BerElement   *ber;
   char         *a, **vals, *dn;
   char         *email;
   char          search[64];
   char          filter[256];
   char          groups[2048];
   char          groupName[256];
   int           v;
   ARBoolean     valid;
   ARBoolean     found;
   ARBoolean     foundGroup;
   ARBoolean     fullAuth = TRUE;
   static char  *attrs[3] = {"cn", "uniquemember", NULL};

   *response = (AREAResponseStruct *) malloc (sizeof(AREAResponseStruct));
   if (*response == NULL)
      return;

   /*
    * If password is NULL then we are:
    * 1) not going to perform a bind (i.e. authenticate) to the
    *    directory service.
    * 2) Only return back e-mail and notification mechanism.  We do
    *    not need to return back group membership or license information.
    */
   if (password == NULL)
      fullAuth = FALSE;

   /* Initialize as a failed login */   
   (*response)->licenseMask  = AREA_LICENSE_MASK_ALL;
   (*response)->licenseWrite = AR_LICENSE_TYPE_NONE;
   (*response)->licenseFTS   = AR_LICENSE_TYPE_NONE;
   (*response)->licenseRes1  = AR_LICENSE_TYPE_NONE;
   (*response)->groups       = NULL;
   (*response)->notifyMech   = AR_NOTIFY_NONE;
   (*response)->email        = NULL;
   (*response)->loginStatus  = AREA_LOGIN_FAILED;
   (*response)->messageText  = NULL;
   (*response)->logText      = NULL;
   (*response)->modTime      = 0;

   ld = ldap_init(MY_HOST, MY_PORT);
   /* get a handle to an LDAP connection */
   if (ld == NULL)
      return;

   /*
    * Authenticate to the directory to do the search.
    */
   if ( ldap_simple_bind_s( ld, NULL, NULL ) != LDAP_SUCCESS)
      return;

   sprintf(search, "%s", MY_PEOPLEBASE);
   sprintf(filter, "(uid=%s)", user);
   if (ldap_search_s(ld, search, LDAP_SCOPE_SUBTREE,
                     filter, NULL, 0, &result) != LDAP_SUCCESS)
      return;

   /*
    * Run through the attributes of each entry looking for a match.
    */
   valid = FALSE;
   found = FALSE;
   email = NULL;
   for (e = ldap_first_entry(ld, result);
        e != NULL;
        e = ldap_next_entry(ld, e))
   {
      found = TRUE;
      dn = ldap_get_dn(ld, e);
      if (dn == NULL)
         continue;

      if (dn[0] == '\0')
         continue;

      if (fullAuth == TRUE &&
          ldap_simple_bind_s(ld, dn, password) != LDAP_SUCCESS)
         continue;

      valid = TRUE;

      /*
       * Get the e-mail address associated with this person.
       */
      for (a = ldap_first_attribute(ld, e, &ber);
           a != NULL;
           a = ldap_next_attribute(ld, e, ber))
      {
         if (strcmp(a, "mail") == 0)
         {
            if (email != NULL)
               free(email);
            email = NULL;

            vals = ldap_get_values( ld, e, a);
            if (vals != NULL)
            {
               email = strdup(vals[0]);
               ldap_value_free(vals);
            }
         }
         ldap_memfree(a);
      }
      if (ber != NULL)
         ber_free(ber, 0);
   }
   ldap_msgfree(result);

   /*
    * Find all of the groups for which this user is a member.
    */
   groups[0] = '\0';
   if ((fullAuth == TRUE) && (found == TRUE) && (valid == TRUE))
   {
      sprintf(search, "%s", MY_GROUPBASE);
      sprintf(filter, "(uniquemember=%s)", dn);

      if (ldap_search_s(ld, search, LDAP_SCOPE_ONELEVEL,
                        filter, attrs, 0, &result) == LDAP_SUCCESS)
      {

         for (e = ldap_first_entry(ld, result);
              e != NULL;
              e = ldap_next_entry(ld, e))
         {
            groupName[0] = '\0';
            for (a = ldap_first_attribute(ld, e, &ber);
                 a != NULL;
                 a = ldap_next_attribute(ld, e, ber))
            {
               /*
                * We are making a basic assumption that the attributes
                * are being returned "in order" - cn first, then the
                * uniquemember.
                */
               if (strcmp(a, "cn") == 0)
               {
                  vals = ldap_get_values( ld, e, a);

                  if (vals[0] != NULL)
                     strcpy(groupName, vals[0]);

                  ldap_value_free(vals);
               }
               else if (strcmp(a, "uniquemember") == 0)
               {
                  vals = ldap_get_values( ld, e, a);

                  for (v = 0, foundGroup = FALSE;
                       (vals[v] != NULL) && (foundGroup == FALSE);
                       v++)
                  {
                     if (strcmp(vals[v], dn) == 0)
                     {
                        sprintf(groups + strlen(groups), "%s; ", groupName);
                        foundGroup = TRUE;
                     }
                  }

                  ldap_value_free(vals);
               }
            }

            ldap_memfree(a);
            if (ber != NULL)
               ber_free(ber, 0);
         }

         ldap_msgfree(result);
      }
   }

   ldap_unbind(ld);

   if ((found == TRUE) && (valid == TRUE || fullAuth == FALSE))
   {
      /*
       * We found a match and either the password is good or
       * we are just processing information for notification
       * purposes.
       */
      (*response)->licenseMask  = AREA_LICENSE_MASK_ALL;
      (*response)->licenseWrite = AR_LICENSE_TYPE_FLOATING;
      (*response)->licenseFTS   = AR_LICENSE_TYPE_NONE;
      (*response)->licenseRes1  = AR_LICENSE_TYPE_NONE;
	  (*response)->licenseApps  = NULL;
      if (fullAuth == TRUE && strlen(groups) > 0)
         (*response)->groups       = strdup(groups);
      else
         (*response)->groups       = NULL;
      (*response)->notifyMech   = AR_NOTIFY_VIA_EMAIL;
      (*response)->email        = email;
      (*response)->loginStatus  = AREA_LOGIN_SUCCESS;
      (*response)->messageText  = NULL;
   }
   else if (found == TRUE)
   {
      /*
       * We found a match and the password is bad.
       */
      (*response)->loginStatus  = AREA_LOGIN_FAILED;
      (*response)->messageText  = NULL;
      free (email);
   }
   else
   {
      /*
       * Otherwise, we do not know the user.
       */
      (*response)->loginStatus  = AREA_LOGIN_UNKNOWN_USER;
      (*response)->messageText  = NULL;
      free (email);
   }

   return;
}


/*****************************************************************************/
/*                                                                           */
/*                             AREANeedToSyncCallback                        */
/*                                                                           */
/*****************************************************************************/
/* Description: This function is called by the AREA core library             */
/*    periodically at the request of the AR System server.                   */
/*                                                                           */
/*    A customized authentication server must define this function.  Any     */
/*    global information or resources accessed here must be protected with   */
/*    appropriate mutual exclusion locks to be multi-thread safe.            */
/*                                                                           */
/*    A non-zero return value will instruct the AR System server to flush    */
/*    its cache of user information.                                         */
/*                                                                           */
/*****************************************************************************/

int AREANeedToSyncCallback(
void                *object
)
{
   return 0;
}


/*****************************************************************************/
/*                                                                           */
/*                               AREAFreeCallback                            */
/*                                                                           */
/*****************************************************************************/
/* Description: This function is called by the AREA core library after a     */
/*    response is made to the AR System Server to authenticate a user.       */
/*                                                                           */
/*    A pointer to the response structure returned by                        */
/*    AREAVerifyLoginCallback() is passed as a parameter.                    */
/*                                                                           */
/*    A customized authentication server must define this function.  Any     */
/*    global information or resources accessed here must be protected with   */
/*    appropriate mutual exclusion locks to be multi-thread safe.            */
/*                                                                           */
/*    A customized authentication server must return a response.  A NULL     */
/*    response will be interpreted as a failed login attempt.                */
/*                                                                           */
/*****************************************************************************/

void AREAFreeCallback(
void               *object,
AREAResponseStruct *response
)
{
   /* Since we allocated the response in AREAVerifyLoginCallback(), we */
   /* should free it here.                                             */

   if (response != NULL)
   {
      if (response->email != NULL)
         free(response->email);
      free (response);
   }

   return;
}


#ifdef _WIN32
BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
					 )
{
   return TRUE;
}

#endif /* _WIN32 */
