/*
 * sslconn.c
 * $Id: sslconn.c,v 1.4 2003/11/16 04:55:14 ttate Exp $
 */

#include "ruby.h"
#include "rbldap.h"
#include <sys/time.h>
#include <unistd.h>

#if defined(HAVE_LDAP_START_TLS_S)
# define USE_OPENLDAP_SSLCONN
#elif defined(HAVE_LDAPSSL_INIT)
# define USE_NSSLDAP_SSLCONN
#elif defined(HAVE_LDAP_SSLINIT)
# define USE_WLDAP32_SSLCONN
#endif

VALUE rb_cLDAP_SSLConn;

#ifdef USE_OPENLDAP_SSLCONN
static VALUE
rb_openldap_sslconn_initialize(int argc, VALUE argv[], VALUE self)
{
  RB_LDAP_DATA *ldapdata;
  LDAP *cldap;
  char *chost = NULL;
  int cport = LDAP_PORT;

  VALUE arg1, arg2, arg3, arg4, arg5;

  LDAPControl **serverctrls = NULL;
  LDAPControl **clientctrls = NULL;
  int version;
  int start_tls;


  Data_Get_Struct(self, RB_LDAP_DATA, ldapdata);
  if( ldapdata->ldap )
    return Qnil;

  switch( rb_scan_args(argc,argv,"05", &arg1, &arg2, &arg3, &arg4, &arg5) ){
  case 0:
    chost = ALLOCA_N(char, strlen("localhost")+1);
    strcpy(chost, "localhost");
    cport = LDAP_PORT;
    start_tls = 0;
    break;
  case 1:
    chost = StringValueCStr(arg1);
    cport = LDAP_PORT;
    start_tls = 0;
    break;
  case 2:
    chost = StringValueCStr(arg1);
    cport = NUM2INT(arg2);
    start_tls = 0;
    break;
  case 3:
    chost = StringValueCStr(arg1);
    cport = NUM2INT(arg2);
    start_tls = (arg3 == Qtrue) ? 1 : 0;
    break;
  case 4:
    chost = StringValueCStr(arg1);
    cport = NUM2INT(arg2);
    start_tls = (arg3 == Qtrue) ? 1 : 0;
    serverctrls = rb_ldap_get_controls(arg4);
    break;
  case 5:
    chost = StringValueCStr(arg1);
    cport = NUM2INT(arg2);
    start_tls = (arg3 == Qtrue) ? 1 : 0;
    serverctrls = rb_ldap_get_controls(arg4);
    clientctrls = rb_ldap_get_controls(arg5);
    break;
  default:
    rb_bug("rb_ldap_conn_new");
  }

  cldap = ldap_init(chost, cport);
  if( ! cldap )
    rb_raise(rb_eLDAP_ResultError, "can't initialize a ldap session");

  ldapdata->ldap = cldap;

  if( rb_block_given_p() ){
    rb_yield(self);
  }

  ldap_get_option(cldap, LDAP_OPT_PROTOCOL_VERSION, &version);
  if( version < LDAP_VERSION3 ){
    version = LDAP_VERSION3;
    ldapdata->err = ldap_set_option(cldap, LDAP_OPT_PROTOCOL_VERSION, &version);
    Check_LDAP_Result(ldapdata->err);
  }

  if( start_tls ){
    ldapdata->err = ldap_start_tls_s(cldap, serverctrls, clientctrls);
    Check_LDAP_Result(ldapdata->err);
  }
  else{
    int opt = LDAP_OPT_X_TLS_HARD;
    ldapdata->err = ldap_set_option(cldap, LDAP_OPT_X_TLS, &opt);
    Check_LDAP_Result(ldapdata->err);
  }

  return Qnil;
}
#endif /* USE_OPENLDAP_SSLCONN */


#ifdef USE_NSSLDAP_SSLCONN
static VALUE
rb_nssldap_sslconn_initialize(int argc, VALUE argv[], VALUE self)
{
  RB_LDAP_DATA *ldapdata;
  LDAP *cldap;
  char *chost = NULL;
  char *certpath = NULL;
  int cport = LDAP_PORT;
  int csecure = 0;

  VALUE arg1, arg2, arg3, arg4;

  Data_Get_Struct(self, RB_LDAP_DATA, ldapdata);
  if( ldapdata->ldap )
    return Qnil;

  switch( rb_scan_args(argc,argv,"04", &arg1, &arg2, &arg3, &arg4) ){
  case 0:
    chost = ALLOCA_N(char, strlen("localhost")+1);
    strcpy(chost, "localhost");
    cport = LDAP_PORT;
    csecure = 0;
    certpath = NULL;
    break;
  case 1:
    chost = StringValueCStr(arg1);
    cport = LDAP_PORT;
    csecure = 0;
    certpath = NULL;
    break;
  case 2:
    chost = StringValueCStr(arg1);
    cport = NUM2INT(arg2);
    csecure = 0;
    certpath = NULL;
    break;
  case 3:
    chost = StringValueCStr(arg1);
    cport = NUM2INT(arg2);
    csecure = (arg3 == Qtrue) ? 1 : 0;
    certpath = NULL;
    break;
  case 4:
    chost = StringValueCStr(arg1);
    cport = NUM2INT(arg2);
    csecure = (arg3 == Qtrue) ? 1 : 0;
    certpath = (arg4 == Qnil) ? NULL : StringValueCStr(arg4);
    break;
  default:
    rb_bug("rb_ldap_conn_new");
  }

  /***
    ldapssl_client_init():
     http://docs.iplanet.com/docs/manuals/dirsdk/csdk41/html/function.htm#25963
     ldapssl_client_authinit():
     http://docs.iplanet.com/docs/manuals/dirsdk/csdk41/html/function.htm#26024
  ***/
  ldapssl_client_init(certpath, NULL);
  cldap = ldapssl_init(chost, cport, csecure);
  ldapdata->ldap = cldap;

  return Qnil;
}
#endif /* USE_NSSLDAP_SSLCONN */

#if defined(USE_WLDAP32_SSLCONN)
static VALUE
rb_wldap32_sslconn_initialize(int argc, VALUE argv[], VALUE self)
{
  RB_LDAP_DATA *ldapdata;
  LDAP *cldap;
  char *chost;
  int cport;
  int csecure;
  int version;

  VALUE arg1, arg2, arg3;

  Data_Get_Struct(self, RB_LDAP_DATA, ldapdata);
  if( ldapdata->ldap )
    return Qnil;

  switch( rb_scan_args(argc,argv,"02",&arg1,&arg2, &arg3) ){
  case 0:
    chost = ALLOCA_N(char, strlen("localhost")+1);
    strcpy(chost, "localhost");
    cport = LDAP_PORT;
    csecure = 1;
    break;
  case 1:
    chost = StringValueCStr(arg1);
    cport = LDAP_PORT;
    csecure = 1;
    break;
  case 2:
    chost = StringValueCStr(arg1);
    cport = NUM2INT(arg2);
    csecure = 1;
    break;
  case 3:
    chost = StringValueCStr(arg1);
    cport = NUM2INT(arg2);
    csecure = (arg3 == Qtrue) ? 1 : 0;
    break;
  default:
    rb_bug("rb_ldap_conn_new");
  }

  cldap = ldap_sslinit(chost,cport,csecure);
  ldapdata->ldap = cldap;

#if defined(HAVE_LDAP_GET_OPTION) && defined(HAVE_LDAP_SET_OPTION)
  ldap_get_option(cldap, LDAP_OPT_PROTOCOL_VERSION, &version);
  if( version < LDAP_VERSION3 ){
    version = LDAP_VERSION3;
    ldapdata->err = ldap_set_option(cldap, LDAP_OPT_PROTOCOL_VERSION, &version);
    Check_LDAP_Result(ldapdata->err);
  }
#endif

  return Qnil;
}

VALUE
rb_ldap_sslconn_bind_s(int argc, VALUE argv[], VALUE self)
{
  RB_LDAP_DATA *ldapdata;

  GET_LDAP_DATA(self,ldapdata);
  ldapdata->err = ldap_connect(ldapdata->ldap, NULL);
  Check_LDAP_Result(ldapdata->err);

  return rb_ldap_conn_bind_s(argc, argv, self);
}

VALUE
rb_ldap_sslconn_simple_bind_s(int argc, VALUE argv[], VALUE self)
{
  RB_LDAP_DATA *ldapdata;

  GET_LDAP_DATA(self,ldapdata);
  ldapdata->err = ldap_connect(ldapdata->ldap, NULL);
  Check_LDAP_Result(ldapdata->err);

  return rb_ldap_conn_simple_bind_s(argc, argv, self);
}
#endif /* USE_WLDAP32_SSLCONN */

VALUE
rb_ldap_sslconn_initialize(int argc, VALUE argv[], VALUE self)
{
#if defined(USE_OPENLDAP_SSLCONN)
  return rb_openldap_sslconn_initialize(argc, argv, self);
#elif defined(USE_NSSLDAP_SSLCONN)
  return rb_nssldap_sslconn_initialize(argc, argv, self);
#elif defined(USE_WLDAP32_SSLCONN)
  return rb_wldap32_sslconn_initialize(argc, argv, self);
#else
  rb_notimplement();
#endif
}

void
Init_ldap_sslconn()
{
  rb_cLDAP_SSLConn = rb_define_class_under(rb_mLDAP, "SSLConn", rb_cLDAP_Conn);
  rb_define_method(rb_cLDAP_SSLConn, "initialize", rb_ldap_sslconn_initialize, -1);
#ifdef USE_WLDAP32_SSLCONN
  rb_define_method(rb_cLDAP_SSLConn, "bind", rb_ldap_sslconn_bind_s, -1);
  rb_define_method(rb_cLDAP_SSLConn, "simple_bind", rb_ldap_sslconn_simple_bind_s, -1);
#endif
}
