/** BEGIN COPYRIGHT BLOCK
 * Copyright (C) 2001 Sun Microsystems, Inc.  Used by permission.
 * Copyright (C) 2005 Red Hat, 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
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 * END COPYRIGHT BLOCK **/
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <ldap.h>
#include "libadminutil/admutil-int.h"
#include "libadminutil/psetc.h"

#ifdef XP_WIN32
#define strcasecmp stricmp
#define strncasecmp _strnicmp
#endif

static char*
admldapCreateLDAPHndl(AdmldapInfo info, char *targetDN, int *error_code)
{
  char            *sieDN = NULL;
  char            *resultDN = NULL;

  if (!info) { *error_code = UG_EMPTY_LDAPINFO; return NULL; }
  *error_code = UG_OP_OK;

  if (targetDN) resultDN = PL_strdup(targetDN);
  else {
    /* No target DN given, try to figure it out */
    if (!sieDN) sieDN = admldapGetSIEDN(info);
    if (sieDN) {
      resultDN = PR_smprintf("cn=configuration,%s", sieDN);
    }
    else { 
      *error_code = UG_NO_TARGETDN; 
    }
  }

  return resultDN;
}

static int
admldapGetUserDirectoryInfo(LDAP *ld, char* targetDN, char** directoryURL,
                        char** bindDN, char** bindPassword,
                        char** directoryInfoRef, int* error_code)
{
  int           ldaperror;
  LDAPMessage   *e, *result;
  struct berval **directoryURLVals = NULL, 
                **bindDNVals = NULL,
                **bindPasswordVals = NULL,
                **directoryInfoRefVals = NULL;

  *error_code = UG_OP_OK;
  *directoryURL = NULL;
  *bindDN = NULL;
  *bindPassword = NULL;
  *directoryInfoRef = NULL;

  if (!ld) { *error_code = UG_NULL_LDAP; return 0; }
  if (!targetDN)  { *error_code = UG_NULL_DN; return 0; }

  if ((ldaperror = ldap_search_ext_s(ld, 
                                     targetDN, 
                                     LDAP_SCOPE_BASE,
                                     "(objectclass=nsDirectoryInfo)", 
                                     NULL, 
                                     0, NULL, NULL, NULL, -1,
                                     &result))
      != LDAP_SUCCESS ) {
    ldap_msgfree(result);
#ifdef LDAP_DEBUG
    fprintf(stderr, "ldap_search_s: [%s:%s] error %d:%s\n",
            targetDN, "(objectclass=nsDirectoryInfo)", ldaperror, ldap_err2string(ldaperror));
#endif
    if (ldaperror == LDAP_INSUFFICIENT_ACCESS ||
        ldaperror == LDAP_INAPPROPRIATE_AUTH) {
      *error_code = UG_ACCESS_FAIL;
      return 0;
    }
    if (ldaperror == LDAP_NO_SUCH_OBJECT) {
      *error_code = UG_ENTRY_NOT_EXIST;
      return 0;
    }
    *error_code = UG_LDAP_SYSTEM_ERR;
    return 0;
  }
  
  if (ldap_count_entries(ld, result) == 0) {
    /* error return : entry does not exist */
    *error_code =  UG_ENTRY_NOT_EXIST;
    ldap_msgfree(result);
    return 0;
  }

  e = ldap_first_entry(ld, result);
  directoryURLVals = ldap_get_values_len(ld, e, "nsDirectoryURL");
  bindDNVals = ldap_get_values_len(ld, e, "nsBindDN");
  bindPasswordVals = ldap_get_values_len(ld, e, "nsBindPassword");
  directoryInfoRefVals = ldap_get_values_len(ld, e, "nsDirectoryInfoRef");

  if (directoryURLVals && directoryURLVals[0]) {
    struct berval **s;
    char buffer[1024];
    
    s = ldap_get_values_len(ld, e, "nsDirectoryFailoverList");
    if (s && s[0] && s[0]->bv_len && s[0]->bv_val && strcmp(s[0]->bv_val, "")) {
      char *temp = PL_strnchr(directoryURLVals[0]->bv_val, '/', directoryURLVals[0]->bv_len);
      /* append failover list to url */
      if (NULL != temp) {
        *temp = '\0';
        PR_snprintf(buffer, sizeof(buffer), "%.*s %.*s/%s",
                    directoryURLVals[0]->bv_len, directoryURLVals[0]->bv_val,
                    s[0]->bv_len, s[0]->bv_val, temp + 1);
      } else {
        PR_snprintf(buffer, sizeof(buffer), "%.*s %.*s",
                    directoryURLVals[0]->bv_len, directoryURLVals[0]->bv_val,
                    s[0]->bv_len, s[0]->bv_val);
      }
    } else {
      PR_snprintf(buffer, sizeof(buffer), "%.*s",
                  directoryURLVals[0]->bv_len, directoryURLVals[0]->bv_val);
    }
    ldap_value_free_len(s);
    
    *directoryURL = PL_strdup(buffer);
    ldap_value_free_len(directoryURLVals);
  }
  if (bindDNVals && bindDNVals[0]) {
    *bindDN = PL_strndup(bindDNVals[0]->bv_val, bindDNVals[0]->bv_len);
    ldap_value_free_len(bindDNVals);
  }
  if (bindPasswordVals && bindPasswordVals[0]) {
    *bindPassword = PL_strndup(bindPasswordVals[0]->bv_val, bindPasswordVals[0]->bv_len);
    /* clear out this memory */
    memset(bindPasswordVals[0]->bv_val, '\0', bindPasswordVals[0]->bv_len);
    ldap_value_free_len(bindPasswordVals);
  }
  if (directoryInfoRefVals && directoryInfoRefVals[0]) {
    *directoryInfoRef = PL_strndup(directoryInfoRefVals[0]->bv_val, directoryInfoRefVals[0]->bv_len);
    ldap_value_free_len(directoryInfoRefVals);
  }
  
  ldap_msgfree(result);
  return 1;
  
}

static char*
admldapGetAdmGroupUGDN(char* sieDN)
{
  char**  dnList;
  int    dnLen = 0;
  char   *dnbuf = NULL;

  dnList = admldap_ldap_explode_dn(sieDN, 0);
  if (!dnList) {
    char *envvar = PR_GetEnv("ADMINUTIL_DEBUG");
    if (envvar && (*envvar == '1')) {
      fprintf(stderr, "Error: could not parse sieDN [%s]\n", sieDN);
    }
    return PL_strdup(sieDN);
  }
  while (dnList && dnList[dnLen]) dnLen++;

  if (dnLen >= 5) {
    dnbuf = PR_smprintf("%s,%s,%s,%s", 
              dnList[dnLen-4],
              dnList[dnLen-3],
              dnList[dnLen-2],
              dnList[dnLen-1]);
  } else {
    char *envvar = PR_GetEnv("ADMINUTIL_DEBUG");
    if (envvar && (*envvar == '1')) {
      fprintf(stderr, "Error: invalid sieDN [%s]\n", sieDN);
    }
    dnbuf = PL_strdup(sieDN);
  }
  admutil_strsFree(dnList);
  return dnbuf;
}

static char*
admldapGetDomainUGDN(char* sieDN)
{
  char**  dnList;
  int    dnLen = 0;
  char   *dnbuf = NULL;

  dnList = admldap_ldap_explode_dn(sieDN, 0);
  if (!dnList) {
    char *envvar = PR_GetEnv("ADMINUTIL_DEBUG");
    if (envvar && (*envvar == '1')) {
      fprintf(stderr, "Error: could not parse sieDN [%s]\n", sieDN);
    }
    return PL_strdup(sieDN);
  }
  while (dnList && dnList[dnLen]) dnLen++;

  if (dnLen >= 3) {
    dnbuf = PR_smprintf("cn=UserDirectory,ou=Global Preferences,%s,%s", 
              dnList[dnLen-2],
              dnList[dnLen-1]);
  } else {
    char *envvar = PR_GetEnv("ADMINUTIL_DEBUG");
    if (envvar && (*envvar == '1')) {
      fprintf(stderr, "Error: invalid sieDN [%s]\n", sieDN);
    }
    dnbuf = PL_strdup(sieDN);
  }
  admutil_strsFree(dnList);

  return dnbuf;
}


PR_IMPLEMENT(int)
admldapGetUserDirectoryReal(LDAP *ld, char* targetDN, char** directoryURL,
                            char** bindDN, char** bindPassword,
                            char** directoryInfoRef, int* error_code)
{

  int         status;
  char        *dummyDirectoryInfoRef = NULL;

  if (!admldapGetUserDirectoryInfo(ld, targetDN, directoryURL, bindDN,
                                   bindPassword, directoryInfoRef, 
                                   error_code)) {
    return 0;
  }

  /* traverse... */
  if (!(*directoryURL)) {
    if (*directoryInfoRef) {
      /* reference is used here  */
      status = admldapGetUserDirectoryReal(ld, 
                                           *directoryInfoRef, 
                                           directoryURL,
                                           bindDN,
                                           bindPassword,
                                           &dummyDirectoryInfoRef, 
                                           error_code);
      if (!status) {
        if(*error_code == UG_ENTRY_NOT_EXIST)
          *error_code = UG_REF_ENTRY_NOT_EXIST;
        return 0;
      }
      *error_code = UG_OP_OK;
      return 1;
    }
    else {
      /* Both directoryURL and directoryInfoRef are empty */
      *error_code = UG_NO_DATA;
      return 0;
    }
  }
  else {
    return 1;
  }
}

PR_IMPLEMENT(int)
admldapSetUserDirectoryReal(LDAP *ld, char* targetDN, char* directoryURL,
                            char* bindDN, char* bindPassword,
                            char* directoryInfoRef, int* error_code)
{
  char       *oldDirectoryURL;
  char       *oldBindDN;
  char       *oldBindPassword;
  char       *oldDirectoryInfoRef;
  int        i, ldaperror;
  LDAPMod    **mods;

  char url_buffer[1024], failover_buffer[1024];
  int url_buflen = sizeof(url_buffer);
  char *temp;

  if (!ld) { *error_code = UG_NULL_LDAP; return 0; }
  if (!targetDN)  { *error_code = UG_NULL_DN; return 0; }

  if ((directoryURL || bindDN || bindPassword) && 
      directoryInfoRef) {
    *error_code = UG_SET_CONFLICT;
    return 0;
  }

  /* verify the existence of reference entry  */
  if (directoryInfoRef) {
    if (!(admldapGetUserDirectoryInfo(ld,
                                      directoryInfoRef,
                                      &oldDirectoryURL,
                                      &oldBindDN,
                                      &oldBindPassword,
                                      &oldDirectoryInfoRef,
                                      error_code))) {
      *error_code = UG_REF_ENTRY_NOT_EXIST;
      return 0;
    }
    if (oldDirectoryURL) PR_Free(oldDirectoryURL);
    if (oldBindDN) PR_Free(oldBindDN);
    if (oldBindPassword) {
      memset(oldBindPassword, '\0', strlen(oldBindPassword));
      PR_Free(oldBindPassword);
      oldBindPassword = NULL;
    }
    if (oldDirectoryInfoRef) PR_Free(oldDirectoryInfoRef);
  }


  *error_code = UG_OP_OK;
  if (!(admldapGetUserDirectoryInfo(ld,
                                    targetDN,
                                    &oldDirectoryURL,
                                    &oldBindDN,
                                    &oldBindPassword,
                                    &oldDirectoryInfoRef,
                                    error_code))) {
    return 0;
  }

  if (!(mods = (LDAPMod**)PR_Malloc(6*sizeof(LDAPMod*)))) {
          *error_code = UG_LDAP_SYSTEM_ERR;
          if (oldDirectoryURL) PR_Free(oldDirectoryURL);
          if (oldBindDN) PR_Free(oldBindDN);
          if (oldBindPassword) {
            memset(oldBindPassword, '\0', strlen(oldBindPassword));
            PR_Free(oldBindPassword);
            oldBindPassword = NULL;
          }
          if (oldDirectoryInfoRef) PR_Free(oldDirectoryInfoRef);
          return 0;
  }

  i = 0;
  if (directoryInfoRef) {
    if (oldDirectoryURL) {
      mods[i++] = createSingleValMod("nsDirectoryFailoverList",
                                     NULL,
                                     LDAP_MOD_DELETE);
      mods[i++] = createSingleValMod("nsDirectoryURL",
                                     NULL,
                                     LDAP_MOD_DELETE);
    }
    if (oldBindDN)
      mods[i++] = createSingleValMod("nsBindDN",
                                     NULL,
                                     LDAP_MOD_DELETE);
    if (oldBindPassword)
      mods[i++] = createSingleValMod("nsBindPassword",
                                     NULL,
                                     LDAP_MOD_DELETE);
    if (oldDirectoryInfoRef)
      mods[i++] = createSingleValMod("nsDirectoryInfoRef",
                                     directoryInfoRef,
                                     LDAP_MOD_REPLACE);
    else
      mods[i++] = createSingleValMod("nsDirectoryInfoRef",
                                     directoryInfoRef,
                                     LDAP_MOD_ADD);
    mods[i] = NULL;
  }
  else {
    if(directoryURL) {

      char * cColonSlashSlash = strstr(directoryURL,"://"); 
      char * cSpace = strchr(cColonSlashSlash+3,' '); 
      char * cSlash = strchr(cColonSlashSlash+3,'/'); 
   
      if ((cSpace!=NULL) && 
          (cSlash!=NULL) && 
          (strcspn(&(cColonSlashSlash[3]), " ") < strcspn(&(cColonSlashSlash[3]), "/"))) {
        /* 
         * failover URL - get first host and port for "nsDirectoryURL", 
         * the rest is for "nsDirectoryFailoverList" 
         */

        PL_strncpy(url_buffer, directoryURL, url_buflen);
        url_buflen -= PL_strlen(directoryURL);
        temp = strchr(url_buffer, ' ');
        if (NULL != temp) {
          PL_strncpy(failover_buffer, &(temp[1]), sizeof(failover_buffer));
          *temp = '\0';
          if ((temp = strrchr(failover_buffer, '/'))) {
            PL_strncat(url_buffer, temp, url_buflen);
            *temp = '\0';
          }
        }
      }
      else {
        PL_strncpy(url_buffer, directoryURL, url_buflen);
        failover_buffer[0] = '\0';
      }
    }
    if (oldDirectoryURL) {
      if(strcmp(url_buffer, "")) {
        mods[i++] = createSingleValMod("nsDirectoryURL",
                                       url_buffer,
                                       LDAP_MOD_REPLACE);
        
        mods[i++] = createSingleValMod("nsDirectoryFailoverList",
                                       failover_buffer,
                                       LDAP_MOD_REPLACE);
      }
    }
    else {
      if(strcmp(url_buffer, "")) {
        mods[i++] = createSingleValMod("nsDirectoryURL",
                                       url_buffer,
                                       LDAP_MOD_ADD);
        
        mods[i++] = createSingleValMod("nsDirectoryFailoverList",
                                       failover_buffer,
                                       LDAP_MOD_ADD);
      }
    }
    if (oldBindDN) {
      if (bindDN)
        mods[i++] = createSingleValMod("nsBindDN",
                                       bindDN,
                                       LDAP_MOD_REPLACE);
      else
        mods[i++] = createSingleValMod("nsBindDN",
                                       NULL,
                                       LDAP_MOD_DELETE);
    }
    else {
      if (bindDN)
        mods[i++] = createSingleValMod("nsBindDN",
                                       bindDN,
                                       LDAP_MOD_ADD);
    }
    
    if (oldBindPassword) {
      if (bindPassword)
        mods[i++] = createSingleValMod("nsBindPassword",
                                       bindPassword,
                                       LDAP_MOD_REPLACE);
      else
        mods[i++] = createSingleValMod("nsBindPassword",
                                       NULL,
                                       LDAP_MOD_DELETE);
    }
    else {
      if (bindPassword)
        mods[i++] = createSingleValMod("nsBindPassword",
                                       bindPassword,
                                       LDAP_MOD_ADD);
    }
    
    if (oldDirectoryInfoRef)
      mods[i++] = createSingleValMod("nsDirectoryInfoRef",
                                     NULL,
                                     LDAP_MOD_DELETE);

    mods[i] = NULL;
  }

  if ( (ldaperror = ldap_modify_ext_s(ld, targetDN, mods, NULL, NULL)) != LDAP_SUCCESS ) {
#ifdef LDAP_DEBUG
    fprintf(stderr, "ldap_modify_s: [%s] error %d:%s\n",
            targetDN, ldaperror, ldap_err2string(ldaperror));
#endif    
    if (ldaperror == LDAP_INSUFFICIENT_ACCESS) *error_code = UG_ACCESS_FAIL;
    else *error_code = UG_LDAP_SYSTEM_ERR;
  }

  deleteMods(mods);
  if (oldDirectoryURL) {
    PR_Free(oldDirectoryURL);
    oldDirectoryInfoRef = NULL;
  }
  if (oldBindDN) {
    PR_Free(oldBindDN);
    oldBindDN = NULL;
  }
  if (oldBindPassword) {
    memset(oldBindPassword, '\0', strlen(oldBindPassword));
    PR_Free(oldBindPassword);
    oldBindPassword = NULL;
  }
  if (oldDirectoryInfoRef) {
    PR_Free(oldDirectoryInfoRef);
    oldDirectoryInfoRef = NULL;
  }
  
  if (*error_code == UG_OP_OK) return 1;
  else return 0;
}

PR_IMPLEMENT(int)
admldapGetUserDirectory(AdmldapInfo info, char* targetDN, char** directoryURL,
                        char** bindDN, char** bindPassword,
                        char** directoryInfoRef, int* error_code)
{
  int             status = 1;
  char            *realTargetDN;

  realTargetDN = admldapCreateLDAPHndl(info, targetDN, error_code);

  if (!realTargetDN) return 0;

  status = admldapGetUserDirectoryReal(admldapGetLDAPHndl(info),
                                       realTargetDN,
                                       directoryURL,
                                       bindDN,
                                       bindPassword,
                                       directoryInfoRef,
                                       error_code);
  
  PR_Free(realTargetDN);
  return status;
}

PR_IMPLEMENT(int)
admldapSetUserDirectory(AdmldapInfo info, char* targetDN, char* directoryURL,
                        char* bindDN, char* bindPassword,
                        char* directoryInfoRef, int* error_code)
{
  int             status = 1;
  char            *realTargetDN;

  realTargetDN = admldapCreateLDAPHndl(info, targetDN, error_code);

  if (!realTargetDN) return 0;

  status = admldapSetUserDirectoryReal(admldapGetLDAPHndl(info),
                                       realTargetDN,
                                       directoryURL,
                                       bindDN,
                                       bindPassword,
                                       directoryInfoRef,
                                       error_code);
  PR_Free(realTargetDN);
  return status;
}


PR_IMPLEMENT(int)
admldapGetLocalUserDirectory(AdmldapInfo info, char** directoryURL,
                             char** bindDN, char** bindPassword,
                             char** directoryInfoRef, int* error_code)
{
  return admldapGetUserDirectory(info,
                                 NULL,
                                 directoryURL,
                                 bindDN,
                                 bindPassword,
                                 directoryInfoRef,
                                 error_code);
}

PR_IMPLEMENT(int)
admldapSetLocalUserDirectory(AdmldapInfo info, char* directoryURL,
                             char* bindDN, char* bindPassword,
                             char* directoryInfoRef, int* error_code)
{
  return admldapSetUserDirectory(info,
                                 NULL,
                                 directoryURL,
                                 bindDN,
                                 bindPassword,
                                 directoryInfoRef,
                                 error_code);
}


PR_IMPLEMENT(int)
admldapGetAdmGrpUserDirectory(AdmldapInfo info, char** directoryURL,
                              char** bindDN, char** bindPassword,
                              char** directoryInfoRef, int* error_code)
{
  char       *admGrpDN;
  int        status;

  admGrpDN = admldapGetAdmGroupUGDN(admldapGetSIEDN(info));

  if (!admGrpDN) {
    *error_code = UG_BAD_DN;
    return 0;
  }

  status = admldapGetUserDirectory(info,
                                 admGrpDN,
                                 directoryURL,
                                 bindDN,
                                 bindPassword,
                                 directoryInfoRef,
                                 error_code);
  PR_Free(admGrpDN);
  return status;
}

PR_IMPLEMENT(int)
admldapSetAdmGrpUserDirectory(AdmldapInfo info, char* directoryURL,
                              char* bindDN, char* bindPassword,
                              char* directoryInfoRef, int* error_code)
{
  char       *admGrpDN;
  int        status;

  admGrpDN = admldapGetAdmGroupUGDN(admldapGetSIEDN(info));

  if (!admGrpDN) {
    *error_code = UG_BAD_DN;
    return 0;
  }

  status = admldapSetUserDirectory(info,
                                 admGrpDN,
                                 directoryURL,
                                 bindDN,
                                 bindPassword,
                                 directoryInfoRef,
                                 error_code);
  PR_Free(admGrpDN);
  return status;
}

PR_IMPLEMENT(int)
admldapGetDomainUserDirectory(AdmldapInfo info, char** directoryURL,
                              char** bindDN, char** bindPassword,
                              char** directoryInfoRef, int* error_code)
{
  
  char       *domainDN;
  int        status;

  domainDN = admldapGetDomainUGDN(admldapGetSIEDN(info));

  if (!domainDN) {
    *error_code = UG_BAD_DN;
    return 0;
  }

  status = admldapGetUserDirectory(info,
                                   domainDN,
                                   directoryURL,
                                   bindDN,
                                   bindPassword,
                                   directoryInfoRef,
                                   error_code);
  PR_Free(domainDN);
  return status;
}


PR_IMPLEMENT(int)
admldapSetDomainUserDirectory(AdmldapInfo info, char* directoryURL,
                              char* bindDN, char* bindPassword,
                              char* directoryInfoRef, int* error_code)
{
  char       *domainDN;
  int        status;

  domainDN = admldapGetDomainUGDN(admldapGetSIEDN(info));

  if (!domainDN) {
    *error_code = UG_BAD_DN;
    return 0;
  }

  status = admldapSetUserDirectory(info,
                                   domainDN,
                                   directoryURL,
                                   bindDN,
                                   bindPassword,
                                   directoryInfoRef,
                                   error_code);
  PR_Free(domainDN);
  return status;
}
