/*
  This file is part of libkldap.
  Copyright (c) 2004-2006 Szombathelyi György <gyurco@freemail.hu>

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Library General  Public
  License as published by the Free Software Foundation; either
  version 2 of the License, or (at your option) any later version.

  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 General Public License for more details.

  You should have received a copy of the GNU Library General Public License
  along with this library; see the file COPYING.LIB.  If not, write to
  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  Boston, MA 02110-1301, USA.
*/

#include "ldapconnection.h"
#include "ldapdefs.h"
#include "kldap_config.h" // SASL2_FOUND, LDAP_FOUND

#include <stdlib.h>
#include <klocale.h>
#include <kdebug.h>

#ifdef SASL2_FOUND
#include <sasl/sasl.h>
static sasl_callback_t callbacks[] = {
  { SASL_CB_ECHOPROMPT, NULL, NULL },
  { SASL_CB_NOECHOPROMPT, NULL, NULL },
  { SASL_CB_GETREALM, NULL, NULL },
  { SASL_CB_USER, NULL, NULL },
  { SASL_CB_AUTHNAME, NULL, NULL },
  { SASL_CB_PASS, NULL, NULL },
  { SASL_CB_CANON_USER, NULL, NULL },
  { SASL_CB_LIST_END, NULL, NULL }
};

static bool ldapoperation_sasl_initialized = false;
#endif

#ifdef LDAP_FOUND
# ifndef HAVE_WINLDAP_H
#  include <lber.h>
#  include <ldap.h>
#else
# include <w32-ldap-help.h>
#endif // HAVE_WINLDAP_H

#ifndef LDAP_OPT_SUCCESS
#define LDAP_OPT_SUCCESS 0
#endif

#endif

using namespace KLDAP;

class LdapConnection::LdapConnectionPrivate
{
  public:
    LdapConnectionPrivate();
    LdapServer mServer;
    QString mConnectionError;

#ifdef LDAP_FOUND
    LDAP *mLDAP;
#else
    void *mLDAP;
#endif
#ifdef SASL2_FOUND
  sasl_conn_t *mSASLconn;
#else
  void *mSASLconn;
#endif

};

LdapConnection::LdapConnectionPrivate::LdapConnectionPrivate()
{
  mSASLconn = 0;
#ifdef SASL2_FOUND
  if ( !ldapoperation_sasl_initialized ) {
    sasl_client_init(NULL);
    ldapoperation_sasl_initialized = true;
  }
#endif
}

LdapConnection::LdapConnection()
  : d( new LdapConnectionPrivate )
{
  d->mLDAP = 0;
}

LdapConnection::LdapConnection( const LdapUrl &url )
  : d( new LdapConnectionPrivate )
{
  d->mLDAP = 0;
  setUrl( url );
}

LdapConnection::LdapConnection( const LdapServer &server )
  : d( new LdapConnectionPrivate )
{
  d->mLDAP = 0;
  setServer( server );
}

LdapConnection::~LdapConnection()
{
  close();
  delete d;
}

void LdapConnection::setUrl( const LdapUrl &url )
{
  d->mServer.setUrl( url );
}

void LdapConnection::setServer( const LdapServer &server )
{
  d->mServer = server;
}

const LdapServer &LdapConnection::server() const
{
  return d->mServer;
}

void *LdapConnection::handle() const
{
  return (void *)d->mLDAP;
}

void *LdapConnection::saslHandle() const
{
  return (void *)d->mSASLconn;
}

QString LdapConnection::errorString( int code )
{
  //No translated error messages yet
#ifdef LDAP_FOUND
  return QString::fromUtf8( ldap_err2string( code ) );
  switch ( code ) {
  case LDAP_OPERATIONS_ERROR:
    return i18n( "LDAP Operations error" );
    //FIXME:
    /* add the LDAP error codes */
  }
#else
  return i18n( "No LDAP Support..." );
#endif
}

QString LdapConnection::saslErrorString() const
{
#ifdef SASL2_FOUND
  const char *str;
  str = sasl_errdetail( d->mSASLconn );
  return QString::fromLocal8Bit( str );
#else
  return i18n( "SASL support is not available. Please recompile libkldap with the "
               "Cyrus-SASL (or compatible) client libraries, or complain to your "
               "distribution packagers." );
#endif
}

QString LdapConnection::connectionError() const
{
  return d->mConnectionError;
}

#ifdef LDAP_FOUND
int LdapConnection::getOption( int option, void *value ) const
{
  Q_ASSERT( d->mLDAP );
  return ldap_get_option( d->mLDAP, option, value );
}

int LdapConnection::setOption( int option, void *value )
{
  Q_ASSERT( d->mLDAP );
  return ldap_set_option( d->mLDAP, option, value );
}

int LdapConnection::ldapErrorCode() const
{
  Q_ASSERT( d->mLDAP );
  int err;
  ldap_get_option( d->mLDAP, LDAP_OPT_ERROR_NUMBER, &err );
  return err;
}

QString LdapConnection::ldapErrorString() const
{
  Q_ASSERT( d->mLDAP );
  char *errmsg;
  ldap_get_option( d->mLDAP, LDAP_OPT_ERROR_STRING, &errmsg );
  QString msg = QString::fromLocal8Bit( errmsg );
  free( errmsg );
  return msg;
}

bool LdapConnection::setSizeLimit( int sizelimit )
{
  Q_ASSERT( d->mLDAP );
  kDebug() << "sizelimit:" << sizelimit;
  if ( setOption( LDAP_OPT_SIZELIMIT, &sizelimit ) != LDAP_OPT_SUCCESS ) {
    return false;
  }
  return true;
}

int LdapConnection::sizeLimit() const
{
  Q_ASSERT( d->mLDAP );
  int sizelimit;
  if ( getOption( LDAP_OPT_SIZELIMIT, &sizelimit ) != LDAP_OPT_SUCCESS ) {
    return -1;
  }
  return sizelimit;
}

bool LdapConnection::setTimeLimit( int timelimit )
{
  Q_ASSERT( d->mLDAP );
  kDebug() << "timelimit:" << timelimit;
  if ( setOption( LDAP_OPT_TIMELIMIT, &timelimit ) != LDAP_OPT_SUCCESS ) {
    return false;
  }
  return true;
}

int LdapConnection::timeLimit() const
{
  Q_ASSERT( d->mLDAP );
  int timelimit;
  if ( getOption( LDAP_OPT_TIMELIMIT, &timelimit ) != LDAP_OPT_SUCCESS ) {
    return -1;
  }
  return timelimit;
}

int LdapConnection::connect()
{
  int ret;
  QString url;
  if ( d->mLDAP ) {
    close();
  }

  int version = d->mServer.version();
  int timeout = d->mServer.timeout();

  url = d->mServer.security() == LdapServer::SSL ? "ldaps" : "ldap";
  url += "://";
  url += d->mServer.host();
  url += ':';
  url += QString::number( d->mServer.port() );
  kDebug() << "ldap url:" << url;
#ifdef HAVE_LDAP_INITIALIZE
  ret = ldap_initialize( &d->mLDAP, url.toLatin1() );
#else
  d->mLDAP = ldap_init( d->mServer.host().toLatin1().data(), d->mServer.port() );
  if ( d->mLDAP == 0 ) {
    ret = -1;
  } else {
    ret = LDAP_SUCCESS;
  }
#endif
  if ( ret != LDAP_SUCCESS ) {
    d->mConnectionError = i18n( "An error occurred during the connection initialization phase." );
    return ret;
  }

  kDebug() << "setting version to:" << version;
  if ( setOption( LDAP_OPT_PROTOCOL_VERSION, &version ) != LDAP_OPT_SUCCESS ) {
    ret = ldapErrorCode();
    d->mConnectionError = i18n( "Cannot set protocol version to %1.", version );
    close();
    return ret;
  }

#if defined(LDAP_OPT_TIMEOUT)
  kDebug() << "setting timeout to:" << timeout;

  if ( timeout ) {
    if ( setOption( LDAP_OPT_TIMEOUT, &timeout ) != LDAP_OPT_SUCCESS ) {
      ret = ldapErrorCode();
      d->mConnectionError = i18np( "Cannot set timeout to %1 second.",
                                   "Cannot set timeout to %1 seconds.",
                                   timeout );
      close();
      return ret;
    }
  }
#endif

  //FIXME: accessing to certificate handling would be good
  kDebug() << "setting security to:" << d->mServer.security();
  if ( d->mServer.security() == LdapServer::TLS ) {
    kDebug() << "start TLS";
#ifdef HAVE_LDAP_START_TLS_S
    if ( ( ret = ldap_start_tls_s( d->mLDAP, NULL, NULL ) ) != LDAP_SUCCESS ) {
      d->mConnectionError = ldapErrorString();
      close();
      return ret;
    }
#else
    close();
    d->mConnectionError = i18n( "TLS support not available in the LDAP client libraries." );
    return -1;
#endif
  }

  kDebug() << "setting sizelimit to:" << d->mServer.sizeLimit();
  if ( d->mServer.sizeLimit() ) {
    if ( !setSizeLimit( d->mServer.sizeLimit() ) ) {
      ret = ldapErrorCode();
      close();
      d->mConnectionError = i18n( "Cannot set size limit." );
      return ret;
    }
  }

  kDebug() << "setting timelimit to:" << d->mServer.timeLimit();
  if ( d->mServer.timeLimit() ) {
    if ( !setTimeLimit( d->mServer.timeLimit() ) ) {
      ret = ldapErrorCode();
      close();
      d->mConnectionError = i18n( "Cannot set time limit." );
      return ret;
    }
  }

#ifdef SASL2_FOUND
  kDebug() << "initializing SASL client";
  int saslresult = sasl_client_new( "ldap", d->mServer.host().toLatin1(),
        0, 0, callbacks, 0, &d->mSASLconn );
  if ( saslresult != SASL_OK ) {
    d->mConnectionError = i18n( "Cannot initialize the SASL client." );
    return KLDAP_SASL_ERROR;
  }
#endif

  return 0;
}

void LdapConnection::close()
{
  if ( d->mLDAP ) {
#ifdef HAVE_LDAP_UNBIND_EXT
    ldap_unbind_ext( d->mLDAP, 0, 0 );
#else
    ldap_unbind( d->mLDAP );
#endif
  }
  d->mLDAP = 0;
#ifdef SASL2_FOUND
  if ( d->mSASLconn ) {
    sasl_dispose( &d->mSASLconn );
    d->mSASLconn = 0;
  }
#endif
  kDebug() << "connection closed!";
}
#else //LDAP_FOUND

int LdapConnection::getOption( int option, void *value ) const
{
  kError() << "No LDAP support...";
  return -1;
}

int LdapConnection::setOption( int option, void *value )
{
  kError() << "No LDAP support...";
  return -1;
}

int LdapConnection::ldapErrorCode() const
{
  kError() << "No LDAP support...";
  return -1;
}

QString LdapConnection::ldapErrorString() const
{
  kError() << "No LDAP support...";
  return QString();
}

bool LdapConnection::setSizeLimit( int sizelimit )
{
  kError() << "No LDAP support...";
  return false;
}

int LdapConnection::sizeLimit() const
{
  kError() << "No LDAP support...";
  return -1;
}

bool LdapConnection::setTimeLimit( int timelimit )
{
  kError() << "No LDAP support...";
  return false;
}

int LdapConnection::timeLimit() const
{
  kError() << "No LDAP support...";
  return -1;
}

int LdapConnection::connect( )
{
  d->mConnectionError =
    i18n( "LDAP support not compiled in. Please recompile libkldap with the "
          "OpenLDAP (or compatible) client libraries, or complain to your "
          "distribution packagers." );
  kError() << "No LDAP support...";
  return -1;
}

void LdapConnection::close()
{
  kError() << "No LDAP support...";
}

#endif
