Commits

Michael Granger  committed 2568fe5

Adding OpenLDAP::Connection#start_tls and TLS option accessors.

  • Participants
  • Parent commits 554953c

Comments (0)

Files changed (16)

 ^lib/openldap_ext\.bundle$
 ^tmp/
 
+\.conf$
+^doc/

File Manifest.txt

 History.md
+Manifest.txt
 README.md
 Rakefile
-bin/openldap
+Roadmap.md
+ext/connection.c
+ext/openldap.c
+ext/openldap.h
 lib/openldap.rb
+lib/openldap/connection.rb
+lib/openldap/exceptions.rb
+lib/openldap/mixins.rb
+lib/openldap/utils.rb
+spec/lib/constants.rb
+spec/lib/helpers.rb
+spec/openldap/connection_spec.rb
+spec/openldap/exceptions_spec.rb
 spec/openldap_spec.rb
+test.conf-example
   - unsolicited notifications
   - continuation references
   - intermediate responses
+  - alias deferencing
   - etc.
 * Cleanly abandon terminated operations where supported
 * Memory-handling cleanup to avoid leaks, corruption, and other
 	self.spec_extras[:licenses] = ["BSD"]
 	self.spec_extras[:signing_key] = '/Volumes/Keys/ged-private_gem_key.pem'
 	self.spec_extras[:extensions] = [ EXTDIR + 'extconf.rb' ]
+	self.extra_rdoc_files += ['ext/connection.c', 'ext/openldap.c']
 
 	self.require_ruby_version( '>= 1.9.2' )
 
 	self.hg_sign_tags = true if self.respond_to?( :hg_sign_tags= )
-	self.yard_opts = [ '--protected', '--verbose' ] if self.respond_to?( :yard_opts= )
-
 	self.rdoc_locations << "deveiate:/usr/local/www/public/code/#{remote_rdoc_dir}"
 end
 
 		cmd = [ 'gdb' ] + GDB_OPTIONS
 		cmd += [ '--args' ]
 		cmd += RSPEC_CMD
-		run( *cmd )
+		system( *cmd )
 	end
 
 	desc "Run the specs under Valgrind."
 	task :valgrind do
 		cmd = [ 'valgrind' ] + VALGRIND_OPTIONS
 		cmd += RSPEC_CMD
-		run( *cmd )
+		system( *cmd )
 	end
 
 end

File ext/connection.c

 #include "openldap.h"
 
 
+#define MILLION_F 1000000.0
+
+
 /* --------------------------------------------------------------
  * Declarations
  * -------------------------------------------------------------- */
 		result = ldap_initialize( &ldp, url );
 		ropenldap_check_result( result, "ldap_initialize( \"%s\" )", url );
 
-		ropenldap_log_obj( self, "debug", "  setting protocol to LDAPv3." );
-		result = ldap_set_option( ldp, LDAP_OPT_PROTOCOL_VERSION, &proto_ver );
-		ropenldap_check_opt_result( result, "LDAP_OPT_PROTOCOL_VERSION" );
-
 		conn = DATA_PTR( self ) = ropenldap_conn_alloc( ldp );
 
 	} else {
 
 
 /*
+ * call-seq:
+ *    conn.protocol_version   -> fixnum
+ *
+ * Get the protocol version use by the connection.
+ *
+ *    conn.protocol_version
+ *    # => 3
+ */
+static VALUE
+ropenldap_conn_protocol_version( VALUE self )
+{
+	struct ropenldap_connection *ptr = ropenldap_get_conn( self );
+	int version = 0;
+
+	if ( ldap_get_option(ptr->ldap, LDAP_OPT_PROTOCOL_VERSION, &version) != LDAP_OPT_SUCCESS )
+		rb_raise( ropenldap_eOpenLDAPError, "couldn't get option: LDAP_OPT_PROTOCOL_VERSION" );
+
+	return INT2FIX( version );
+}
+
+
+/*
+ * call-seq:
+ *    conn.protocol_version = version
+ *
+ * Set the protocol version use by the connection to +version+.
+ *
+ *    conn.protocol_version = 3
+ *    # => 3
+ */
+static VALUE
+ropenldap_conn_protocol_version_eq( VALUE self, VALUE version )
+{
+	struct ropenldap_connection *ptr = ropenldap_get_conn( self );
+	int v = NUM2INT( version );
+
+	if ( ldap_set_option(ptr->ldap, LDAP_OPT_PROTOCOL_VERSION, &v) != LDAP_OPT_SUCCESS )
+		rb_raise( ropenldap_eOpenLDAPError, "couldn't set option: LDAP_OPT_PROTOCOL_VERSION" );
+
+	return version;
+}
+
+
+/*
+ * call-seq:
+ *    conn.async_connect?   -> boolean
+ *
+ * Returns +true+ if the connect(2) call made by the library will be made asynchronously.
+ *
+ *    conn.async_connect?
+ *    # => true
+ */
+static VALUE
+ropenldap_conn_async_connect_p( VALUE self )
+{
+	struct ropenldap_connection *ptr = ropenldap_get_conn( self );
+	int enabled;
+
+	if ( ldap_get_option(ptr->ldap, LDAP_OPT_CONNECT_ASYNC, &enabled) != LDAP_OPT_SUCCESS )
+		rb_raise( ropenldap_eOpenLDAPError, "couldn't get option: LDAP_OPT_CONNECT_ASYNC" );
+
+	return enabled ? Qtrue : Qfalse;
+}
+
+
+/*
+ * call-seq:
+ *    conn.async_connect = boolean
+ *
+ * If set to a +true+ value, the library will call connect(2) and return without waiting for
+ * a response.
+ *
+ *    conn.async_connect = true
+ */
+static VALUE
+ropenldap_conn_async_connect_eq( VALUE self, VALUE boolean )
+{
+	struct ropenldap_connection *ptr = ropenldap_get_conn( self );
+	int rv = 0;
+
+	if ( RTEST(boolean) ) {
+		ropenldap_log_obj( self, "debug", "Enabling async connect." );
+		rv = ldap_set_option( ptr->ldap, LDAP_OPT_CONNECT_ASYNC, LDAP_OPT_ON );
+	} else {
+		ropenldap_log_obj( self, "debug", "Disabling async connect." );
+		rv = ldap_set_option( ptr->ldap, LDAP_OPT_CONNECT_ASYNC, LDAP_OPT_OFF );
+	}
+
+	if ( rv != LDAP_OPT_SUCCESS )
+		rb_raise( ropenldap_eOpenLDAPError, "couldn't set option: LDAP_OPT_CONNECT_ASYNC" );
+
+	return RTEST( boolean ) ? Qtrue : Qfalse;
+}
+
+
+/*
+ * call-seq:
+ *    conn.network_timeout   -> float or nil
+ *
+ * Returns the network timeout value (if it is set).
+ *
+ *    conn.network_timeout
+ *    # => 2.5
+ */
+static VALUE
+ropenldap_conn_network_timeout( VALUE self )
+{
+	struct ropenldap_connection *ptr = ropenldap_get_conn( self );
+	struct timeval *timeout;
+	double seconds = 0;
+
+	if ( ldap_get_option(ptr->ldap, LDAP_OPT_NETWORK_TIMEOUT, &timeout) != LDAP_OPT_SUCCESS )
+		rb_raise( ropenldap_eOpenLDAPError, "couldn't get option: LDAP_OPT_NETWORK_TIMEOUT" );
+
+	if ( timeout ) {
+		ropenldap_log_obj( self, "debug", "Got network timeout: %d/%d",
+		                   timeout->tv_sec, timeout->tv_usec );
+		seconds = ((double) timeout->tv_sec) + timeout->tv_usec / MILLION_F;
+		ldap_memfree( timeout );
+		return rb_float_new( seconds );
+	} else {
+		ropenldap_log_obj( self, "debug", "No network timeout." );
+		return Qnil;
+	}
+}
+
+
+/*
+ * call-seq:
+ *    conn.network_timeout = float or nil
+ *
+ * Set the network timeout value; the network timeout value is the number of seconds after which
+ * poll(2)/select(2) following a connect(2) returns in case of no activity. Setting this to nil
+ * or -1 disables it.
+ *
+ *    conn.network_timeout = 1.0
+ */
+static VALUE
+ropenldap_conn_network_timeout_eq( VALUE self, VALUE arg )
+{
+	struct ropenldap_connection *ptr = ropenldap_get_conn( self );
+	double seconds;
+	struct timeval timeout;
+
+	if ( NIL_P(arg) ) {
+		seconds = -1.0;
+	} else {
+		seconds = NUM2DBL( arg );
+	}
+
+	ropenldap_log_obj( self, "debug", "Setting network timeout to %0.5f seconds", seconds );
+	timeout.tv_sec = (time_t)floor( seconds );
+	timeout.tv_usec = (suseconds_t)( fmod(seconds, 1.0) * MILLION_F );
+
+	if ( ldap_set_option(ptr->ldap, LDAP_OPT_NETWORK_TIMEOUT, (const void *)&timeout) != LDAP_OPT_SUCCESS )
+		rb_raise( ropenldap_eOpenLDAPError, "couldn't set option: LDAP_OPT_NETWORK_TIMEOUT" );
+
+	return arg;
+}
+
+
+/*
+ * call-seq:
+ *    conn.simple_bind( bind_dn, password )   -> result
+ *    conn.simple_bind( bind_dn, password ) {|result| ... }
+ *
+ * Bind to the directory using a simple +bind_dn+ and a +password+.
+ *
+ */
+static VALUE
+ropenldap_conn_simple_bind( VALUE self, VALUE bind_dn, VALUE password )
+{
+	struct ropenldap_connection *ptr = ropenldap_get_conn( self );
+	return Qnil;
+}
+
+
+/*
+ * Start TLS synchronously; called from ropenldap_conn__start_tls after
+ * the GIL is released.
+ */
+static VALUE
+ropenldap_conn__start_tls_body( void *ptr )
+{
+	LDAP *ld = ptr;
+	return (VALUE)ldap_start_tls_s( ld, NULL, NULL );
+}
+
+
+/*
+ * #_start_ls: backend of the #start_tls method.
+ */
+static VALUE
+ropenldap_conn__start_tls( VALUE self )
+{
+	struct ropenldap_connection *ptr = ropenldap_get_conn( self );
+
+	int result;
+
+	ropenldap_log_obj( self, "debug", "Starting TLS..." );
+	result = (int)rb_thread_blocking_region( ropenldap_conn__start_tls_body, (void *)ptr->ldap,
+		RUBY_UBF_IO, NULL );
+	ropenldap_check_result( result, "ldap_start_tls_s" );
+	ropenldap_log_obj( self, "debug", "  TLS started." );
+
+	return Qtrue;
+}
+
+
+/*
+ * call-seq:
+ *    conn.tls_inplace?   -> true or false
+ *
+ * Returns +true+ if TLS handlers have been installed on the session.
+ *
+ */
+static VALUE
+ropenldap_conn_tls_inplace_p( VALUE self )
+{
+#ifdef HAVE_LDAP_TLS_INPLACE
+	struct ropenldap_connection *ptr = ropenldap_get_conn( self );
+
+	if ( ldap_tls_inplace(ptr->ldap) ) {
+		return Qtrue;
+	} else {
+		return Qfalse;
+	}
+#else
+	rb_raise( rb_eNotImpError, "not implemented in your version of libldap" );
+#endif /* HAVE_LDAP_TLS_INPLACE */
+}
+
+
+/*
+ * call-seq:
+ *    conn.tls_cacertfile    -> string
+ *
+ * Get the full path of the CA certificate file as a String.
+ */
+static VALUE
+ropenldap_conn_tls_cacertfile( VALUE self )
+{
+	struct ropenldap_connection *ptr = ropenldap_get_conn( self );
+	char *path;
+	VALUE pathstring = Qnil;
+
+	if ( ldap_get_option(ptr->ldap, LDAP_OPT_X_TLS_CACERTFILE, &path) != LDAP_OPT_SUCCESS )
+		rb_raise( ropenldap_eOpenLDAPError, "couldn't get option: LDAP_OPT_X_TLS_CACERTFILE" );
+	if ( !path )
+		return Qnil;
+
+	pathstring = rb_str_new2( path );
+	ldap_memfree( path );
+
+	return pathstring;
+}
+
+/*
+ * call-seq:
+ *    conn.tls_cacertfile = string
+ *
+ * Set the full path of the CA certificate file as a String.
+ */
+static VALUE
+ropenldap_conn_tls_cacertfile_eq( VALUE self, VALUE path )
+{
+	struct ropenldap_connection *ptr = ropenldap_get_conn( self );
+	VALUE pathstring = rb_obj_as_string( path );
+	const char *pathopt = StringValuePtr( pathstring );
+
+	if ( ldap_set_option(ptr->ldap, LDAP_OPT_X_TLS_CACERTFILE, pathopt) != LDAP_OPT_SUCCESS )
+		rb_raise( ropenldap_eOpenLDAPError, "couldn't set option: LDAP_OPT_X_TLS_CACERTFILE" );
+
+	return path;
+}
+
+
+/*
+ * call-seq:
+ *    conn.tls_cacertdir    -> string
+ *
+ * Get the full path of the directory containing CA certificates as a String.
+ */
+static VALUE
+ropenldap_conn_tls_cacertdir( VALUE self )
+{
+	struct ropenldap_connection *ptr = ropenldap_get_conn( self );
+	char *path;
+	VALUE pathstring = Qnil;
+
+	if ( ldap_get_option(ptr->ldap, LDAP_OPT_X_TLS_CACERTDIR, &path) != LDAP_OPT_SUCCESS )
+		rb_raise( ropenldap_eOpenLDAPError, "couldn't get option: LDAP_OPT_X_TLS_CACERTDIR" );
+	if ( !path )
+		return Qnil;
+
+	pathstring = rb_str_new2( path );
+	ldap_memfree( path );
+
+	return pathstring;
+}
+
+/*
+ * call-seq:
+ *    conn.tls_cacertdir = string
+ *
+ * Set the path of the directory containing CA certificates as a String.
+ */
+static VALUE
+ropenldap_conn_tls_cacertdir_eq( VALUE self, VALUE path )
+{
+	struct ropenldap_connection *ptr = ropenldap_get_conn( self );
+	VALUE pathstring = rb_obj_as_string( path );
+	const char *pathopt = StringValuePtr( pathstring );
+
+	if ( ldap_set_option(ptr->ldap, LDAP_OPT_X_TLS_CACERTDIR, pathopt) != LDAP_OPT_SUCCESS )
+		rb_raise( ropenldap_eOpenLDAPError, "couldn't set option: LDAP_OPT_X_TLS_CACERTDIR" );
+
+	return path;
+}
+
+
+/*
+ * call-seq:
+ *    conn.tls_cacertfile    -> string
+ *
+ * Get the full path of the certificate file as a String.
+ */
+static VALUE
+ropenldap_conn_tls_certfile( VALUE self )
+{
+	struct ropenldap_connection *ptr = ropenldap_get_conn( self );
+	char *path;
+	VALUE pathstring = Qnil;
+
+	if ( ldap_get_option(ptr->ldap, LDAP_OPT_X_TLS_CERTFILE, &path) != LDAP_OPT_SUCCESS )
+		rb_raise( ropenldap_eOpenLDAPError, "couldn't get option: LDAP_OPT_X_TLS_CERTFILE" );
+	if ( !path )
+		return Qnil;
+
+	pathstring = rb_str_new2( path );
+	ldap_memfree( path );
+
+	return pathstring;
+}
+
+/*
+ * call-seq:
+ *    conn.tls_certfile = string
+ *
+ * Set the full path of the certificate file as a String.
+ */
+static VALUE
+ropenldap_conn_tls_certfile_eq( VALUE self, VALUE path )
+{
+	struct ropenldap_connection *ptr = ropenldap_get_conn( self );
+	VALUE pathstring = rb_obj_as_string( path );
+	const char *pathopt = StringValuePtr( pathstring );
+
+	if ( ldap_set_option(ptr->ldap, LDAP_OPT_X_TLS_CERTFILE, pathopt) != LDAP_OPT_SUCCESS )
+		rb_raise( ropenldap_eOpenLDAPError, "couldn't set option: LDAP_OPT_X_TLS_CERTFILE" );
+
+	return path;
+}
+
+
+/*
+ * call-seq:
+ *    conn.tls_keyfile    -> string
+ *
+ * Get the full path of the file that contains the private key that matches the certificate stored
+ * in the #tls_certfile.
+ */
+static VALUE
+ropenldap_conn_tls_keyfile( VALUE self )
+{
+	struct ropenldap_connection *ptr = ropenldap_get_conn( self );
+	char *path;
+	VALUE pathstring = Qnil;
+
+	if ( ldap_get_option(ptr->ldap, LDAP_OPT_X_TLS_KEYFILE, &path) != LDAP_OPT_SUCCESS )
+		rb_raise( ropenldap_eOpenLDAPError, "couldn't get option: LDAP_OPT_X_TLS_KEYFILE" );
+	if ( !path )
+		return Qnil;
+
+	pathstring = rb_str_new2( path );
+	ldap_memfree( path );
+
+	return pathstring;
+}
+
+/*
+ * call-seq:
+ *    conn.tls_keyfile = newvalue
+ *
+ * Set the full path to the file that contains the private key that matches the certificate stored
+ * in the #tls_certfile.
+ */
+static VALUE
+ropenldap_conn_tls_keyfile_eq( VALUE self, VALUE path )
+{
+	struct ropenldap_connection *ptr = ropenldap_get_conn( self );
+	VALUE pathstring = rb_obj_as_string( path );
+	const char *pathopt = StringValuePtr( pathstring );
+
+	if ( ldap_set_option(ptr->ldap, LDAP_OPT_X_TLS_KEYFILE, pathopt) != LDAP_OPT_SUCCESS )
+		rb_raise( ropenldap_eOpenLDAPError, "couldn't set option: LDAP_OPT_X_TLS_KEYFILE" );
+
+	return path;
+}
+
+
+/*
+ * call-seq:
+ *    conn.tls_cipher_suite    -> cipherstring
+ *
+ * Get the allowed cipher suite. See http://www.openssl.org/docs/apps/ciphers.html for the
+ * allowed format of the +cipherstring+.
+ */
+static VALUE
+ropenldap_conn_tls_cipher_suite( VALUE self )
+{
+	struct ropenldap_connection *ptr = ropenldap_get_conn( self );
+	char *path;
+	VALUE pathstring = Qnil;
+
+	if ( ldap_get_option(ptr->ldap, LDAP_OPT_X_TLS_CIPHER_SUITE, &path) != LDAP_OPT_SUCCESS )
+		rb_raise( ropenldap_eOpenLDAPError, "couldn't get option: LDAP_OPT_X_TLS_CIPHER_SUITE" );
+	if ( !path )
+		return Qnil;
+
+	pathstring = rb_str_new2( path );
+	ldap_memfree( path );
+
+	return pathstring;
+}
+
+/*
+ * call-seq:
+ *    conn.tls_cipher_suite = cipherstring
+ *
+ * Set the allowed cipher suite to +cipherstring+; see http://www.openssl.org/docs/apps/ciphers.html
+ * for more about the format of this string.
+ */
+static VALUE
+ropenldap_conn_tls_cipher_suite_eq( VALUE self, VALUE cipherstring )
+{
+	struct ropenldap_connection *ptr = ropenldap_get_conn( self );
+	VALUE string = rb_obj_as_string( cipherstring );
+	const char *cipheropt = StringValuePtr( string );
+
+	if ( ldap_set_option(ptr->ldap, LDAP_OPT_X_TLS_CIPHER_SUITE, cipheropt) != LDAP_OPT_SUCCESS )
+		rb_raise( ropenldap_eOpenLDAPError, "couldn't set option: LDAP_OPT_X_TLS_CIPHER_SUITE" );
+
+	return string;
+}
+
+
+/*
+ * call-seq:
+ *    conn.tls_random_file    -> true or false
+ *
+ * Get the path to the random file that will be used when /dev/random and /dev/urandom are
+ * not available.
+ */
+static VALUE
+ropenldap_conn_tls_random_file( VALUE self )
+{
+	struct ropenldap_connection *ptr = ropenldap_get_conn( self );
+	char *path;
+	VALUE pathstring = Qnil;
+
+	if ( ldap_get_option(ptr->ldap, LDAP_OPT_X_TLS_RANDOM_FILE, &path) != LDAP_OPT_SUCCESS )
+		rb_raise( ropenldap_eOpenLDAPError, "couldn't get option: LDAP_OPT_X_TLS_RANDOM_FILE" );
+	if ( !path )
+		return Qnil;
+
+	pathstring = rb_str_new2( path );
+	ldap_memfree( path );
+
+	return pathstring;
+}
+
+/*
+ * call-seq:
+ *    conn.tls_random_file = newvalue
+ *
+ * Set the path to the random file that will be used when /dev/random and /dev/urandom are
+ * not available.
+ */
+static VALUE
+ropenldap_conn_tls_random_file_eq( VALUE self, VALUE path )
+{
+	struct ropenldap_connection *ptr = ropenldap_get_conn( self );
+	VALUE pathstring = rb_obj_as_string( path );
+	const char *pathopt = StringValuePtr( pathstring );
+
+	if ( ldap_set_option(ptr->ldap, LDAP_OPT_X_TLS_RANDOM_FILE, pathopt) != LDAP_OPT_SUCCESS )
+		rb_raise( ropenldap_eOpenLDAPError, "couldn't set option: LDAP_OPT_X_TLS_RANDOM_FILE" );
+
+	return path;
+}
+
+
+/*
+ * call-seq:
+ *    conn.tls_dhfile    -> string
+ *
+ * Path to PEM encoded Diffie-Hellman parameter file.
+ */
+static VALUE
+ropenldap_conn_tls_dhfile( VALUE self )
+{
+	struct ropenldap_connection *ptr = ropenldap_get_conn( self );
+	char *path;
+	VALUE pathstring = Qnil;
+
+	if ( ldap_get_option(ptr->ldap, LDAP_OPT_X_TLS_DHFILE, &path) != LDAP_OPT_SUCCESS )
+		rb_raise( ropenldap_eOpenLDAPError, "couldn't get option: LDAP_OPT_X_TLS_DHFILE" );
+	if ( !path )
+		return Qnil;
+
+	pathstring = rb_str_new2( path );
+	ldap_memfree( path );
+
+	return pathstring;
+}
+
+/*
+ * call-seq:
+ *    conn.tls_dhfile = newvalue
+ *
+ * Set the path to a PEM-encoded Diffie-Hellman parameter file. If this is specified, DH key 
+ * exchange will be used for the ephemeral keying. 
+ * 
+ * You can use the 'openssl' command-line tool to generate the file like so:
+ * 
+ *   openssl dhparam -outform PEM -out dh1024.pem -5 1024
+ */
+static VALUE
+ropenldap_conn_tls_dhfile_eq( VALUE self, VALUE path )
+{
+	struct ropenldap_connection *ptr = ropenldap_get_conn( self );
+	VALUE pathstring = rb_obj_as_string( path );
+	const char *pathopt = StringValuePtr( pathstring );
+
+	if ( ldap_set_option(ptr->ldap, LDAP_OPT_X_TLS_DHFILE, pathopt) != LDAP_OPT_SUCCESS )
+		rb_raise( ropenldap_eOpenLDAPError, "couldn't set option: LDAP_OPT_X_TLS_DHFILE" );
+
+	return path;
+}
+
+
+/*
+ * call-seq:
+ *    conn.tls_crlfile    -> string
+ *
+ * Get the current path to the file containing a Certificate Revocation List used for verifying that 
+ * certificates have not been revoked.
+ */
+static VALUE
+ropenldap_conn_tls_crlfile( VALUE self )
+{
+	struct ropenldap_connection *ptr = ropenldap_get_conn( self );
+	char *path;
+	VALUE pathstring = Qnil;
+
+	if ( ldap_get_option(ptr->ldap, LDAP_OPT_X_TLS_CRLFILE, &path) != LDAP_OPT_SUCCESS )
+		rb_raise( ropenldap_eOpenLDAPError, "couldn't get option: LDAP_OPT_X_TLS_CRLFILE" );
+	if ( !path )
+		return Qnil;
+
+	pathstring = rb_str_new2( path );
+	ldap_memfree( path );
+
+	return pathstring;
+}
+
+/*
+ * call-seq:
+ *    conn.tls_crlfile = path
+ *
+ * Set the path to the file containing a Certificate Revocation List used for verifying that 
+ * certificates have not been revoked. This value is only used when LDAP has been compiled
+ * to use GnuTLS.
+ */
+static VALUE
+ropenldap_conn_tls_crlfile_eq( VALUE self, VALUE path )
+{
+	struct ropenldap_connection *ptr = ropenldap_get_conn( self );
+	VALUE pathstring = rb_obj_as_string( path );
+	const char *pathopt = StringValuePtr( pathstring );
+
+	if ( ldap_set_option(ptr->ldap, LDAP_OPT_X_TLS_CRLFILE, pathopt) != LDAP_OPT_SUCCESS )
+		rb_raise( ropenldap_eOpenLDAPError, "couldn't set option: LDAP_OPT_X_TLS_CRLFILE" );
+
+	return path;
+}
+
+
+/*
+ * call-seq:
+ *    conn._tls_require_cert    -> fixnum
+ *
+ * Backend method for #tls_require_cert
+ */
+static VALUE
+ropenldap_conn__tls_require_cert( VALUE self )
+{
+	struct ropenldap_connection *ptr = ropenldap_get_conn( self );
+	int opt;
+
+	if ( ldap_get_option(ptr->ldap, LDAP_OPT_X_TLS_REQUIRE_CERT, &opt) != LDAP_OPT_SUCCESS )
+		rb_raise( ropenldap_eOpenLDAPError, "couldn't get option: LDAP_OPT_X_TLS_REQUIRE_CERT" );
+
+	return INT2FIX( opt );
+}
+
+/*
+ * call-seq:
+ *    conn._tls_require_cert = fixnum
+ *
+ * Backend method for #tls_require_cert=.
+ */
+static VALUE
+ropenldap_conn__tls_require_cert_eq( VALUE self, VALUE opt )
+{
+	struct ropenldap_connection *ptr = ropenldap_get_conn( self );
+	const int optval = NUM2INT( opt );
+
+	if ( ldap_set_option(ptr->ldap, LDAP_OPT_X_TLS_REQUIRE_CERT, &optval) != LDAP_OPT_SUCCESS )
+		rb_raise( ropenldap_eOpenLDAPError, "couldn't set option: LDAP_OPT_X_TLS_REQUIRE_CERT" );
+
+	return opt;
+}
+
+
+/*
+ * call-seq:
+ *    conn._tls_crlcheck    -> fixnum
+ *
+ * Backend method for #tls_crlcheck.
+ */
+static VALUE
+ropenldap_conn__tls_crlcheck( VALUE self )
+{
+	struct ropenldap_connection *ptr = ropenldap_get_conn( self );
+	int opt;
+
+	if ( ldap_get_option(ptr->ldap, LDAP_OPT_X_TLS_CRLCHECK, &opt) != LDAP_OPT_SUCCESS )
+		rb_raise( ropenldap_eOpenLDAPError, "couldn't get option: LDAP_OPT_X_TLS_CRLCHECK" );
+
+	return INT2FIX( opt );
+}
+
+/*
+ * call-seq:
+ *    conn._tls_crlcheck = fixnum
+ *
+ * Backend method for #tls_crlcheck=.
+ */
+static VALUE
+ropenldap_conn__tls_crlcheck_eq( VALUE self, VALUE opt )
+{
+	struct ropenldap_connection *ptr = ropenldap_get_conn( self );
+	const int optval = NUM2INT( opt );
+
+	if ( ldap_set_option(ptr->ldap, LDAP_OPT_X_TLS_CRLCHECK, &optval) != LDAP_OPT_SUCCESS )
+		rb_raise( ropenldap_eOpenLDAPError, "couldn't set option: LDAP_OPT_X_TLS_CRLCHECK" );
+
+	return opt;
+}
+
+
+/*
+ * call-seq:
+ *    conn.tls_protocol_min    -> fixnum
+ *
+ * Gets the current minimum protocol version.
+ */
+static VALUE
+ropenldap_conn_tls_protocol_min( VALUE self )
+{
+	struct ropenldap_connection *ptr = ropenldap_get_conn( self );
+	int version;
+
+	if ( ldap_get_option(ptr->ldap, LDAP_OPT_X_TLS_PROTOCOL_MIN, &version) != LDAP_OPT_SUCCESS )
+		rb_raise( ropenldap_eOpenLDAPError, "couldn't get option: LDAP_OPT_X_TLS_PROTOCOL_MIN" );
+
+	return INT2FIX( version );
+}
+
+
+/*
+ * call-seq:
+ *    conn.tls_protocol_min = fixnum
+ *
+ * Set the minimum protocol version.
+ */
+static VALUE
+ropenldap_conn_tls_protocol_min_eq( VALUE self, VALUE opt )
+{
+	struct ropenldap_connection *ptr = ropenldap_get_conn( self );
+	const int optval = NUM2INT( opt );
+
+	if ( ldap_set_option(ptr->ldap, LDAP_OPT_X_TLS_PROTOCOL_MIN, &optval) != LDAP_OPT_SUCCESS )
+		rb_raise( ropenldap_eOpenLDAPError, "couldn't set option: LDAP_OPT_X_TLS_PROTOCOL_MIN" );
+
+	return opt;
+}
+
+
+/*
+ * call-seq:
+ *    conn.tls_package    -> string
+ *
+ * :FIXME: I can't find any docs on what this does.
+ */
+static VALUE
+ropenldap_conn_tls_package( VALUE self )
+{
+#ifdef LDAP_OPT_X_TLS_PACKAGE
+	struct ropenldap_connection *ptr = ropenldap_get_conn( self );
+	char *package;
+	VALUE pkgstring = Qnil;
+
+	if ( ldap_get_option(ptr->ldap, LDAP_OPT_X_TLS_PACKAGE, &package) != LDAP_OPT_SUCCESS )
+		rb_raise( ropenldap_eOpenLDAPError, "couldn't get option: LDAP_OPT_X_TLS_PACKAGE" );
+	if ( !package )
+		return Qnil;
+
+	pkgstring = rb_str_new2( package );
+	ldap_memfree( package );
+
+	return pkgstring;
+#else
+	rb_raise( rb_eNotImpError, "not implemented in your version of libldap." );
+#endif /* LDAP_OPT_X_TLS_PACKAGE */
+}
+
+
+/*
+ * call-seq:
+ *    conn.create_new_tls_context    -> true or false
+ *
+ * Instructs the library to create a new TLS library context.
+ */
+static VALUE
+ropenldap_conn_create_new_tls_context( VALUE self )
+{
+	struct ropenldap_connection *ptr = ropenldap_get_conn( self );
+	const int flag = 1;
+
+	if ( ldap_set_option(ptr->ldap, LDAP_OPT_X_TLS_NEWCTX, &flag) != LDAP_OPT_SUCCESS )
+		rb_raise( ropenldap_eOpenLDAPError, "couldn't set option: LDAP_OPT_X_TLS_NEWCTX" );
+
+	return Qtrue;
+}
+
+
+/*
  * Turn a STRING_T into a URI object via URI::parse.
  */
 static VALUE
  * call-seq:
  *     connection.uris   -> array
  *
- * Gets an Array of URIs to be contacted by the library when trying to establish 
+ * Gets an Array of URIs to be contacted by the library when trying to establish
  * a connection.
  *
  */
 	struct ropenldap_connection *ptr = ropenldap_get_conn( self );
 	char *uris;
 	VALUE uristring, uriarray;
-	int result;
 
-	result = ldap_get_option( ptr->ldap, LDAP_OPT_URI, &uris );
-	ropenldap_check_opt_result( result, "LDAP_OPT_PROTOCOL_VERSION" );
+	if ( ldap_get_option(ptr->ldap, LDAP_OPT_URI, &uris) != LDAP_OPT_SUCCESS )
+		rb_raise( ropenldap_eOpenLDAPError, "couldn't get option: LDAP_OPT_URI" );
 
 	/* Convert to strings first, then collect them into URI objects */
 	uristring = rb_str_new2( uris );
 	ldap_memfree( uris );
 
 	uriarray = rb_funcall( uristring, rb_intern("split"), 0 );
-	rb_block_call( uriarray, rb_intern("collect!"), 0, NULL, 
+	rb_block_call( uriarray, rb_intern("collect!"), 0, NULL,
 	               ropenldap_parse_uri, Qnil );
 
 	return uriarray;
 }
 
 
+/*
+ * call-seq:
+ *    connection.fdno   -> fixnum
+ *
+ * Return the file descriptor of the underlying socket. If the socket isn't connected yet,
+ * this method returns +nil+.
+ *
+ *    ldapsock = IO.for_fd( conn.fdno, "w+" )
+ */
+static VALUE
+ropenldap_conn_fdno( VALUE self )
+{
+	struct ropenldap_connection *ptr = ropenldap_get_conn( self );
+	int fdno = 0;
+
+	if ( ldap_get_option(ptr->ldap, LDAP_OPT_DESC, &fdno) != LDAP_OPT_SUCCESS )
+		rb_raise( ropenldap_eOpenLDAPError, "couldn't get option: LDAP_OPT_DESC" );
+
+	/* If the socket isn't set up yet, return nil */
+	if ( fdno < 0 ) return Qnil;
+
+	return INT2FIX( fdno );
+}
+
+
+
 
 /*
- * OpenLDAP Connection class
+ * document-class: OpenLDAP::Connection
  */
 void
 ropenldap_init_connection( void )
 
 	rb_define_alloc_func( ropenldap_cOpenLDAPConnection, ropenldap_conn_s_allocate );
 
-	rb_define_method( ropenldap_cOpenLDAPConnection, "initialize", ropenldap_conn_initialize, -2 );
+	rb_define_protected_method( ropenldap_cOpenLDAPConnection, "_initialize",
+	                            ropenldap_conn_initialize, -2 );
 
 	rb_define_method( ropenldap_cOpenLDAPConnection, "uris", ropenldap_conn_uris, 0 );
+	rb_define_method( ropenldap_cOpenLDAPConnection, "fdno", ropenldap_conn_fdno, 0 );
+	rb_define_alias(  ropenldap_cOpenLDAPConnection, "fileno", "fdno" );
+	rb_define_method( ropenldap_cOpenLDAPConnection, "simple_bind", ropenldap_conn_simple_bind, 2 );
+
+	rb_define_method( ropenldap_cOpenLDAPConnection, "protocol_version",
+	                  ropenldap_conn_protocol_version, 0 );
+	rb_define_method( ropenldap_cOpenLDAPConnection, "protocol_version=",
+	                  ropenldap_conn_protocol_version_eq, 1 );
+	rb_define_method( ropenldap_cOpenLDAPConnection, "async_connect?",
+	                  ropenldap_conn_async_connect_p, 0 );
+	rb_define_method( ropenldap_cOpenLDAPConnection, "async_connect=",
+	                  ropenldap_conn_async_connect_eq, 1 );
+	rb_define_method( ropenldap_cOpenLDAPConnection, "network_timeout",
+	                  ropenldap_conn_network_timeout, 0 );
+	rb_define_method( ropenldap_cOpenLDAPConnection, "network_timeout=",
+	                  ropenldap_conn_network_timeout_eq, 1 );
+
+	rb_define_method( ropenldap_cOpenLDAPConnection, "tls_inplace?",
+	                  ropenldap_conn_tls_inplace_p, 0 );
+
+	/* Options */
+	rb_define_method( ropenldap_cOpenLDAPConnection, "tls_cacertfile",
+	                  ropenldap_conn_tls_cacertfile, 0 );
+	rb_define_method( ropenldap_cOpenLDAPConnection, "tls_cacertfile=",
+	                  ropenldap_conn_tls_cacertfile_eq, 1 );
+	rb_define_method( ropenldap_cOpenLDAPConnection, "tls_cacertdir",
+	                  ropenldap_conn_tls_cacertdir, 0 );
+	rb_define_method( ropenldap_cOpenLDAPConnection, "tls_cacertdir=",
+	                  ropenldap_conn_tls_cacertdir_eq, 1 );
+	rb_define_method( ropenldap_cOpenLDAPConnection, "tls_certfile",
+	                  ropenldap_conn_tls_certfile, 0 );
+	rb_define_method( ropenldap_cOpenLDAPConnection, "tls_certfile=",
+	                  ropenldap_conn_tls_certfile_eq, 1 );
+	rb_define_method( ropenldap_cOpenLDAPConnection, "tls_keyfile",
+	                  ropenldap_conn_tls_keyfile, 0 );
+	rb_define_method( ropenldap_cOpenLDAPConnection, "tls_keyfile=",
+	                  ropenldap_conn_tls_keyfile_eq, 1 );
+	rb_define_method( ropenldap_cOpenLDAPConnection, "tls_cipher_suite",
+	                  ropenldap_conn_tls_cipher_suite, 0 );
+	rb_define_method( ropenldap_cOpenLDAPConnection, "tls_cipher_suite=",
+	                  ropenldap_conn_tls_cipher_suite_eq, 1 );
+	rb_define_method( ropenldap_cOpenLDAPConnection, "tls_random_file",
+	                  ropenldap_conn_tls_random_file, 0 );
+	rb_define_method( ropenldap_cOpenLDAPConnection, "tls_random_file=",
+	                  ropenldap_conn_tls_random_file_eq, 1 );
+	rb_define_method( ropenldap_cOpenLDAPConnection, "tls_dhfile",
+	                  ropenldap_conn_tls_dhfile, 0 );
+	rb_define_method( ropenldap_cOpenLDAPConnection, "tls_dhfile=",
+	                  ropenldap_conn_tls_dhfile_eq, 1 );
+	rb_define_method( ropenldap_cOpenLDAPConnection, "tls_crlfile",
+	                  ropenldap_conn_tls_crlfile, 0 );
+	rb_define_method( ropenldap_cOpenLDAPConnection, "tls_crlfile=",
+	                  ropenldap_conn_tls_crlfile_eq, 1 );
+
+	/* Integer options */
+	rb_define_method( ropenldap_cOpenLDAPConnection, "tls_protocol_min",
+	                  ropenldap_conn_tls_protocol_min, 0 );
+	rb_define_method( ropenldap_cOpenLDAPConnection, "tls_protocol_min=",
+	                  ropenldap_conn_tls_protocol_min_eq, 1 );
+
+	/* Read-only options */
+	rb_define_method( ropenldap_cOpenLDAPConnection, "tls_package",
+	                  ropenldap_conn_tls_package, 0 );
+
+	rb_define_method( ropenldap_cOpenLDAPConnection, "create_new_tls_context",
+	                  ropenldap_conn_create_new_tls_context, 0 );
+
+	/* Methods with Ruby front-ends */
+	rb_define_protected_method( ropenldap_cOpenLDAPConnection, "_start_tls",
+	                            ropenldap_conn__start_tls, 0 );
+	rb_define_protected_method( ropenldap_cOpenLDAPConnection, "_tls_require_cert",
+	                            ropenldap_conn__tls_require_cert, 0 );
+	rb_define_protected_method( ropenldap_cOpenLDAPConnection, "_tls_require_cert=",
+	                            ropenldap_conn__tls_require_cert_eq, 1 );
+	rb_define_protected_method( ropenldap_cOpenLDAPConnection, "_tls_crlcheck",
+	                            ropenldap_conn__tls_crlcheck, 0 );
+	rb_define_protected_method( ropenldap_cOpenLDAPConnection, "_tls_crlcheck=",
+	                            ropenldap_conn__tls_crlcheck_eq, 1 );
+
+	rb_require( "openldap/connection" );
 }
 

File ext/extconf.rb

 
 if ENV['MAINTAINER_MODE']
 	$stderr.puts "** Maintainer mode enabled. **"
-	$CFLAGS << ' -Wall' << ' -ggdb' << ' -DDEBUG'
+	$CFLAGS << ' -Wall' << ' -Wno-unused' << " -O0" << ' -ggdb' << ' -DDEBUG'
 end
 
 
 have_const( 'LDAP_API_VERSION', 'ldap.h' ) or
 	abort "no LDAP_API_VERSION constant defined"
 
+have_func( 'ldap_tls_inplace' )
+
+create_header()
 create_makefile( 'openldap_ext' )

File ext/openldap.c

 
 
 /*
- * Raise an appropriate exception for the given option +resultcode+ if one is
- * warranted.
- */
-void
-ropenldap_check_opt_result( int optresult, const char *opt )
-{
-	VALUE exception_class = Qnil;
-
-	if ( optresult == LDAP_OPT_SUCCESS ) return;
-	if ( optresult == LDAP_OPT_ERROR )
-		rb_raise( rb_eRuntimeError, "Failed to get/set %s option!", opt );
-
-	exception_class =
-		rb_funcall( ropenldap_eOpenLDAPError, rb_intern("subclass_for"), 1, INT2FIX(optresult) );
-	rb_raise( exception_class, "while getting/setting %s", opt );
-}
-
-
-/*
  * Convert an array of string pointers to a Ruby Array of Strings.
  */
 VALUE
  *          :vendor_name=>"OpenLDAP", :vendor_version=>20424} (using ==)
  */
 static VALUE
-ropenldap_api_info( VALUE self )
+ropenldap_s_api_info( VALUE self )
 {
 	VALUE rval = rb_hash_new();
 	LDAPAPIInfo info;
  *    # => 
  */
 static VALUE
-ropenldap_api_feature_info( VALUE self )
+ropenldap_s_api_feature_info( VALUE self )
 {
 	VALUE rval = rb_hash_new();
 	int i;
 }
 
 
+/*
+ * call-seq:
+ *    OpenLDAP.uris   -> array
+ *
+ * Return 
+ *
+ *    example code
+ */
+static VALUE
+ropenldap_s_uris( VALUE self )
+{
+	VALUE rval;
+	char *uris;
+
+	if ( ldap_get_option(NULL, LDAP_OPT_URI, &uris) != LDAP_SUCCESS )
+		rb_raise( ropenldap_eOpenLDAPError, "ldap_get_option(URI) failed." );
+
+	rval = rb_str_new2( uris );
+	ldap_memfree( uris );
+
+	return rb_funcall( rval, rb_intern("split"), 1, rb_str_new(" ", 1) );
+}
 
 
 
 
 	rb_require( "openldap/mixins" );
 	ropenldap_mOpenLDAPLoggable = rb_define_module_under( ropenldap_mOpenLDAP, "Loggable" );
-	ropenldap_eOpenLDAPError = rb_define_class_under( ropenldap_mOpenLDAP, "Error", rb_eRuntimeError );
+	ropenldap_eOpenLDAPError =
+		rb_define_class_under( ropenldap_mOpenLDAP, "Error", rb_eRuntimeError );
 
 	/* Constants */
 
 	/* versions */
 	rb_define_const( ropenldap_mOpenLDAP, "LDAP_API_VERSION", INT2FIX(LDAP_API_VERSION) );
 
+	/* Ports */
+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_PORT", INT2FIX(LDAP_PORT) );
+	rb_define_const( ropenldap_mOpenLDAP, "LDAPS_PORT", INT2FIX(LDAPS_PORT) );
+
+	/* RFC constants */
+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_ROOT_DSE", rb_str_new2(LDAP_ROOT_DSE) );
+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_NO_ATTRS", rb_str_new2(LDAP_NO_ATTRS) );
+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_ALL_USER_ATTRIBUTES",
+	                 rb_str_new2(LDAP_ALL_USER_ATTRIBUTES) );
+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_ALL_OPERATIONAL_ATTRIBUTES",
+				     rb_str_new2(LDAP_ALL_OPERATIONAL_ATTRIBUTES) );
+
+	/* RFC4511 maxInt */
+ 	rb_define_const( ropenldap_mOpenLDAP, "LDAP_MAXINT", INT2NUM(LDAP_MAXINT) );
+
 	/* search scopes */
 	rb_define_const( ropenldap_mOpenLDAP, "LDAP_SCOPE_BASE", INT2FIX(LDAP_SCOPE_BASE) );
 	rb_define_const( ropenldap_mOpenLDAP, "LDAP_SCOPE_BASEOBJECT", INT2FIX(LDAP_SCOPE_BASEOBJECT) );
 	rb_define_const( ropenldap_mOpenLDAP, "LDAP_REFERRAL_LIMIT_EXCEEDED", INT2FIX(LDAP_REFERRAL_LIMIT_EXCEEDED) );
 	rb_define_const( ropenldap_mOpenLDAP, "LDAP_X_CONNECTING", INT2FIX(LDAP_X_CONNECTING) );
 
+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_OPT_X_TLS_NEVER", INT2FIX(LDAP_OPT_X_TLS_NEVER) );
+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_OPT_X_TLS_HARD", INT2FIX(LDAP_OPT_X_TLS_HARD) );
+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_OPT_X_TLS_DEMAND", INT2FIX(LDAP_OPT_X_TLS_DEMAND) );
+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_OPT_X_TLS_ALLOW", INT2FIX(LDAP_OPT_X_TLS_ALLOW) );
+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_OPT_X_TLS_TRY", INT2FIX(LDAP_OPT_X_TLS_TRY) );
+
+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_OPT_X_TLS_CRL_NONE", INT2FIX(LDAP_OPT_X_TLS_CRL_NONE) );
+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_OPT_X_TLS_CRL_PEER", INT2FIX(LDAP_OPT_X_TLS_CRL_PEER) );
+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_OPT_X_TLS_CRL_ALL", INT2FIX(LDAP_OPT_X_TLS_CRL_ALL) );
+
 	/* Module functions */
 	rb_define_singleton_method( ropenldap_mOpenLDAP, "split_url", ropenldap_s_split_url, 1 );
 	rb_define_singleton_method( ropenldap_mOpenLDAP, "err2string", ropenldap_s_err2string, 1 );
-	rb_define_singleton_method( ropenldap_mOpenLDAP, "api_info", ropenldap_api_info, 0 );
-	rb_define_singleton_method( ropenldap_mOpenLDAP, "api_feature_info", ropenldap_api_feature_info, 0 );
+	rb_define_singleton_method( ropenldap_mOpenLDAP, "api_info", ropenldap_s_api_info, 0 );
+	rb_define_singleton_method( ropenldap_mOpenLDAP, "api_feature_info", ropenldap_s_api_feature_info, 0 );
+
+	rb_define_singleton_method( ropenldap_mOpenLDAP, "uris", ropenldap_s_uris, 0 );
 
 	/* Initialize the other parts of the extension */
 	ropenldap_init_connection();

File ext/openldap.h

 #define __OPENLDAP_H__
 
 #include <stdio.h>
+#include <math.h>
 #include <string.h>
 #include <inttypes.h>
 #include <assert.h>
 
 #include <ruby.h>
 
-
+#include "extconf.h"
 
 /* --------------------------------------------------------------
  * Globals
 void ropenldap_check_result( int, va_dcl );
 #endif
 
-void ropenldap_check_opt_result         _(( int, const char * ));
-
 VALUE ropenldap_rb_string_array         _(( char ** ));
 
 

File lib/openldap.rb

 
 # The namespace for OpenLDAP classes.
 # 
-# @author Michael Granger <ged@FaerieMUD.org>
+# == Authors
+# 
+# * Michael Granger <ged@FaerieMUD.org>
 # 
 module OpenLDAP
 
 	require 'openldap/utils'
 
 	### Logging
+
 	# Log levels
 	LOG_LEVELS = {
 		'debug' => Logger::DEBUG,
 
 	end
 
-	# Load the Ruby parts of the library
+	# Load the remaining Ruby parts of the library
 	require 'openldap/exceptions'
 
 end # module OpenLDAP
 
+
+# Allow some backward-compatibility with ruby-ldap
+unless defined?( ::LDAP )
+	::LDAP = ::OpenLDAP
+end
+
+

File lib/openldap/connection.rb

 #!/usr/bin/env ruby
 
+require 'uri'
 require 'openldap' unless defined?( OpenLDAP )
 
-# class OpenLDAP::Connection
-# 
-# 	# No-op for now
-# 
-# end # class OpenLDAP::Connection
-# 
+# OpenLDAP Connection class
+class OpenLDAP::Connection
+
+	# Default options for new OpenLDAP::Connections.
+	DEFAULT_OPTIONS = {
+		:protocol_version => 3,
+	}
+
+	# Default TLS options to set before STARTTLS
+	DEFAULT_TLS_OPTIONS = {}
+
+	# Mapping of names of TLS peer certificate-checking strategies into Fixnum values used by 
+	# the underlying library.
+	TLS_REQUIRE_CERT_STRATEGIES = {
+		:never  => OpenLDAP::LDAP_OPT_X_TLS_NEVER,
+		:hard   => OpenLDAP::LDAP_OPT_X_TLS_HARD,
+		:demand => OpenLDAP::LDAP_OPT_X_TLS_DEMAND,
+		:allow  => OpenLDAP::LDAP_OPT_X_TLS_ALLOW,
+		:try    => OpenLDAP::LDAP_OPT_X_TLS_TRY
+	}
+
+	# Inverse of TLS_REQUIRE_CERT_STRATEGIES
+	TLS_REQUIRE_CERT_STRATEGY_NAMES = TLS_REQUIRE_CERT_STRATEGIES.invert
+
+	# Mapping of names of TLS CRL evaluation strategies into Fixnum values used by 
+	# the underlying library.
+	TLS_CRL_CHECK_STRATEGIES = {
+		:none => OpenLDAP::LDAP_OPT_X_TLS_CRL_NONE,
+		:peer => OpenLDAP::LDAP_OPT_X_TLS_CRL_PEER,
+		:all  => OpenLDAP::LDAP_OPT_X_TLS_CRL_ALL
+	}
+
+	# Inverse of TLS_CRL_CHECK_STRATEGIES
+	TLS_CRL_CHECK_STRATEGY_NAMES = TLS_CRL_CHECK_STRATEGIES.invert
+
+
+	### Create a new OpenLDAP::Connection object that will attempt to connect to one of the
+	### specified +urls+ in order.
+	def initialize( *urls )
+		options = if urls.last.is_a?( Hash ) then urls.pop else {} end
+		options = DEFAULT_OPTIONS.merge( options )
+
+		url_strings = urls.map( &self.method(:simplify_url) )
+		self._initialize( url_strings )
+
+		# Set options
+		options.each do |opt, val|
+			case opt
+			when :timeout
+				self.network_timeout = Float( val )
+			else
+				if self.respond_to?( "#{opt}=" )
+					self.send( "#{opt}=", val )
+				else
+					self.log.info "Unknown option %p: ignoring" % [ opt ]
+				end
+			end
+		end
+	end
+
+
+	######
+	public
+	######
+
+	### Initiate TLS processing on the LDAP session. If called without a block, the call returns
+	### when TLS handlers have been installed. If called with the block, the call runs asyncronously
+	### and calls the block when TLS is installed. If there is an error, or TLS is already set up on
+	### the connection, an appropriate OpenLDAP::Error is raised.
+	###
+	###    conn.start_tls( :tls_require_cert => :try )
+	###
+	def start_tls( options=DEFAULT_TLS_OPTIONS )
+		options.each do |opt, val|
+			if opt.to_s.index( 'tls_' ) != 0
+				self.log.info "Skipping non-TLS option: %p" % [ opt ]
+				next
+			end
+
+			self.send( "#{opt}=", val )
+		end
+
+		self._start_tls
+	end
+
+
+	### Get the current peer certificate-checking strategy (a Symbol). See #tls_require_cert=
+	### for a list of the valid return values and what they mean.
+	def tls_require_cert
+		sym = TLS_REQUIRE_CERT_STRATEGY_NAMES[ self._tls_require_cert ] or
+			raise IndexError, "unknown TLS certificate-checking strategy %p" %
+				[self._tls_require_cert]
+		return sym
+	end
+
+
+	### Set the current peer certificate-checking +strategy+ (a Symbol). One of:
+	###
+	### [:never]  This is the default. The library will not ask the peer for a certificate.
+	### [:allow]  The peer certificate is requested. If no certificate is provided, the session 
+	###           proceeds normally. If a bad certificate is provided, it will be ignored and the 
+	###           session proceeds normally.
+	### [:try]    The peer certificate is requested. If no certificate is provided, the session
+	###           proceeds normally. If a bad certificate is provided, the session is immediately
+	###           terminated.
+	### [:demand] The peer certificate is requested. If no certificate is provided, or a bad 
+	###           certificate is provided, the session is immediately terminated.
+    ###          
+	### Note that a valid client certificate is required in order to use the SASL EXTERNAL
+	### authentication mechanism with a TLS session. As such, a non-default
+	### setting must be chosen to enable SASL EXTERNAL authentication.
+	def tls_require_cert=( strategy )
+		numeric_opt = TLS_REQUIRE_CERT_STRATEGIES[ strategy ] or
+			raise IndexError, "unknown TLS certificate-checking strategy %p" % [strategy]
+		self._tls_require_cert=( numeric_opt )
+	end
+
+
+	### Get the current CRL check strategy (a Symbol). See #tls_crlcheck=
+	### for a list of the valid return values and what they mean.
+	def tls_crlcheck
+		sym = TLS_CRL_CHECK_STRATEGY_NAMES[ self._tls_crlcheck ] or
+			raise IndexError, "unknown TLS CRL evaluation strategy %p" % [self._tls_crlcheck]
+		return sym
+	end
+
+
+	### Specify if the Certificate Revocation List (CRL) of the CA should be used to check
+	### if the client certificates have been revoked or not. This option is ignored with GNUtls. 
+	### +strategy+ can be specified as one of the following:
+	###
+	### [:none]   No CRL checks are performed
+	### [:peer]   Check the CRL of the peer certificate
+	### [:all]    Check the CRL for a whole certificate chain
+	###
+	### If this is set to +:peer+ or +:all+, #tls_cacertdir also needs to be set.
+	def tls_crlcheck=( strategy )
+		numeric_opt = TLS_CRL_CHECK_STRATEGIES[ strategy ] or
+			raise IndexError, "unknown TLS CRL evaluation strategy %p" % [strategy]
+		self._tls_crlcheck=( numeric_opt )
+	end
+
+
+	### Fetch an IO object wrapped around the file descriptor the library is using to 
+	### communicate with the directory. Returns +nil+ if the connection hasn't yet
+	### been established.
+	def socket
+		unless @socket
+			fd = self.fdno or return nil
+			@socket = IO.for_fd( fd, "rb:ascii-8bit" )
+			@socket.autoclose = false
+			@socket.close_on_exec = false
+		end
+
+		return @socket
+	end
+
+
+	#######
+	private
+	#######
+
+	### Strip all but the schema, host, and port from the given +url+ and return it as a
+	### String. 
+	def simplify_url( url )
+		url = URI( url ) unless url.is_a?( URI )
+		simpleurl = URI::Generic.build( :scheme => url.scheme, :host => url.host, :port => url.port )
+		self.log.info "Simplified URL %s to: %s" % [ url, simpleurl ]
+
+		return simpleurl.to_s
+	end
+
+end # class OpenLDAP::Connection
+

File lib/openldap/exceptions.rb

 	RESULT_EXCEPTION_CLASS = {}
 
 	# The base class for all OpenLDAP exceptions
+	#
+	# The exception class hierarchy follows the error constants specified by the OpenLDAP
+	# client library, and looks like this:
+	# 
+	# * OpenLDAP::Error
+	#   * Referral
+	#   * OperationsError
+	#   * ProtocolError
+	#   * TimelimitExceeded
+	#   * SizelimitExceeded
+	#   * CompareFalse
+	#   * CompareTrue
+	#   * AuthMethodNotSupported
+	#   * StrongAuthRequired
+	#   * PartialResults
+	#   * AdminlimitExceeded
+	#   * UnavailableCriticalExtension
+	#   * ConfidentialityRequired
+	#   * SASLBindInProgress
+	#   * AttrError
+	#     * NoSuchAttribute
+	#     * UndefinedType
+	#     * InappropriateMatching
+	#     * ConstraintViolation
+	#     * TypeOrValueExists
+	#     * InvalidSyntax
+	#   * NameError
+	#     * NoSuchObject
+	#     * AliasProblem
+	#     * InvalidDNSyntax
+	#     * IsLeaf
+	#     * AliasDerefProblem
+	#   * SecurityError
+	#     * XProxyAuthzFailure
+	#     * InappropriateAuth
+	#     * InvalidCredentials
+	#     * InsufficientAccess
+	#   * ServiceError
+	#     * Busy
+	#     * Unavailable
+	#     * UnwillingToPerform
+	#     * LoopDetect
+	#   * UpdateError
+	#     * NamingViolation
+	#     * ObjectClassViolation
+	#     * NotAllowedOnNonleaf
+	#     * NotAllowedOnRdn
+	#     * AlreadyExists
+	#     * NoObjectClassMods
+	#     * ResultsTooLarge
+	#     * AffectsMultipleDSAs
+	#     * VLVError
+	#   * OtherError
+	#   * APIError
+	#     * ServerDown
+	#     * LocalError
+	#     * EncodingError
+	#     * DecodingError
+	#     * Timeout
+	#     * AuthUnknown
+	#     * FilterError
+	#     * UserCancelled
+	#     * ParamError
+	#     * NoMemory
+	#     * ConnectError
+	#     * NotSupported
+	#     * ControlNotFound
+	#     * NoResultsReturned
+	#     * MoreResultsToReturn
+	#     * ClientLoop
+	#     * ReferralLimitExceeded
+	#     * XConnecting
 	class Error < RuntimeError
 
 		# The result code that corresponds to the exception type
 	def_ldap_exception :SASLBindInProgress, LDAP_SASL_BIND_IN_PROGRESS
 
 	#define LDAP_ATTR_ERROR(n)	LDAP_RANGE((n),0x10,0x15) /* 16-21 */
-	class AttrError < OpenLDAP::Error; end
+	class AttrError < OpenLDAP::Error # :nodoc:
+	end
 
 	def_ldap_exception :NoSuchAttribute, LDAP_NO_SUCH_ATTRIBUTE, OpenLDAP::AttrError
 	def_ldap_exception :UndefinedType, LDAP_UNDEFINED_TYPE, OpenLDAP::AttrError
 	def_ldap_exception :InvalidSyntax, LDAP_INVALID_SYNTAX, OpenLDAP::AttrError
 
 	#define LDAP_NAME_ERROR(n)	LDAP_RANGE((n),0x20,0x24) /* 32-34,36 */
-	class NameError < OpenLDAP::Error; end
+	class NameError < OpenLDAP::Error # :nodoc:
+	end
 
 	def_ldap_exception :NoSuchObject, LDAP_NO_SUCH_OBJECT, OpenLDAP::NameError
 	def_ldap_exception :AliasProblem, LDAP_ALIAS_PROBLEM, OpenLDAP::NameError
 	def_ldap_exception :AliasDerefProblem, LDAP_ALIAS_DEREF_PROBLEM, OpenLDAP::NameError
 
 	#define LDAP_SECURITY_ERROR(n)	LDAP_RANGE((n),0x2F,0x32) /* 47-50 */
-	class SecurityError < OpenLDAP::Error; end
+	class SecurityError < OpenLDAP::Error # :nodoc:
+	end
 
 	def_ldap_exception :XProxyAuthzFailure, LDAP_X_PROXY_AUTHZ_FAILURE, OpenLDAP::SecurityError
 	def_ldap_exception :InappropriateAuth, LDAP_INAPPROPRIATE_AUTH, OpenLDAP::SecurityError
 	def_ldap_exception :InsufficientAccess, LDAP_INSUFFICIENT_ACCESS, OpenLDAP::SecurityError
 
 	#define LDAP_SERVICE_ERROR(n)	LDAP_RANGE((n),0x33,0x36) /* 51-54 */
-	class ServiceError < OpenLDAP::Error; end
+	class ServiceError < OpenLDAP::Error # :nodoc:
+	end
 
 	def_ldap_exception :Busy, LDAP_BUSY, OpenLDAP::ServiceError
 	def_ldap_exception :Unavailable, LDAP_UNAVAILABLE, OpenLDAP::ServiceError
 	def_ldap_exception :LoopDetect, LDAP_LOOP_DETECT, OpenLDAP::ServiceError
 
 	#define LDAP_UPDATE_ERROR(n)	LDAP_RANGE((n),0x40,0x47) /* 64-69,71 */
-	class UpdateError < OpenLDAP::Error; end
+	class UpdateError < OpenLDAP::Error # :nodoc:
+	end
 
 	def_ldap_exception :NamingViolation, LDAP_NAMING_VIOLATION, OpenLDAP::UpdateError
 	def_ldap_exception :ObjectClassViolation, LDAP_OBJECT_CLASS_VIOLATION, OpenLDAP::UpdateError
 	def_ldap_exception :VLVError, LDAP_VLV_ERROR if defined?( OpenLDAP::LDAP_VLV_ERROR )
 
 	# Implementation-specific errors
-	class OtherError < OpenLDAP::Error; end
+	class OtherError < OpenLDAP::Error # :nodoc:
+	end
 	RESULT_EXCEPTION_CLASS.default = OpenLDAP::OtherError
 
 	# API Error Codes
 	# Based on draft-ietf-ldap-c-api-xx
 	# but with new negative code values
 	# 
-	class APIError < OpenLDAP::Error; end
+	class APIError < OpenLDAP::Error # :nodoc:
+	end
 
 	def_ldap_exception :ServerDown, LDAP_SERVER_DOWN, OpenLDAP::APIError
 	def_ldap_exception :LocalError, LDAP_LOCAL_ERROR, OpenLDAP::APIError

File lib/openldap/utils.rb

 module OpenLDAP # :nodoc:
 
 	# A alternate formatter for Logger instances.
-	class LogFormatter < Logger::Formatter
+	class LogFormatter < Logger::Formatter # :nodoc:
 
 		# The format to output unless debugging is turned on
 		DEFAULT_FORMAT = "[%1$s.%2$06d %3$d/%4$s] %5$5s -- %7$s\n"
 
 	# An alternate formatter for Logger instances that outputs +dd+ HTML
 	# fragments.
-	class HtmlLogFormatter < Logger::Formatter
+	class HtmlLogFormatter < Logger::Formatter # :nodoc:
 		include ERB::Util  # for html_escape()
 
 		# The default HTML fragment that'll be used as the template for each log message.

File spec/lib/constants.rb

 
 	unless defined?( TEST_LDAP_URI )
 
+		TEST_CONFIG_FILE = Pathname( __FILE__ ).dirname.parent.parent + 'test.conf'
+
 		TEST_LOCAL_LDAP_STRING = 'ldap://localhost'
 		TEST_LOCAL_LDAP_URI = URI( TEST_LOCAL_LDAP_STRING )
 
 		TEST_LDAP_STRING = 'ldap://ldap.example.com'
 		TEST_LDAP_URI = URI( TEST_LDAP_STRING )
 
+		TEST_LDAPBASE_STRING = 'ldap://ldap.example.com/dc=example,dc=com'
+		TEST_LDAPBASE_URI = URI( TEST_LDAPBASE_STRING )
+
 		constants.each do |cname|
 			const_get(cname).freeze
 		end

File spec/lib/helpers.rb

 	end
 
 
+	### Load the testing LDAP config options from a YAML file.
+	def load_ldap_config
+		unless defined?( @ldap_config ) && @ldap_config
+			if TEST_CONFIG_FILE.exist?
+				$stderr.puts "Loading LDAP config from #{TEST_CONFIG_FILE}" if $VERBOSE
+				@ldap_config = YAML.load( TEST_CONFIG_FILE.read )
+			else
+				$stderr.puts "Skipping tests that require access to a live directory. Copy the ",
+					"#{TEST_CONFIG_FILE}-example file and provide valid values for testing",
+					"with an actual LDAP."
+				@ldap_config = {}
+			end
+		end
+
+		return @ldap_config
+	end
+
 end
 
 
 ### Mock with Rspec
-Rspec.configure do |config|
+RSpec.configure do |config|
+	include OpenLDAP::TestConstants
+
 	config.mock_with :rspec
-
-	config.extend( OpenLDAP::TestConstants )
-
-	config.include( OpenLDAP::TestConstants )
 	config.include( OpenLDAP::SpecHelpers )
 
 	config.filter_run_excluding( :ruby_1_9_only => true ) if vvec( RUBY_VERSION ) >= vvec( '1.9.0' )
+	config.filter_run_excluding( :with_ldap_server => true ) unless TEST_CONFIG_FILE.exist?
 end
 
 # vim: set nosta noet ts=4 sw=4:

File spec/openldap/connection_spec.rb

 		conn.uris.should == [ TEST_LDAP_URI, TEST_LOCAL_LDAP_URI ]
 	end
 
-	describe "a connection instance" do
+	context "an instance" do
 
 		before( :each ) do
 			@conn = OpenLDAP::Connection.new( TEST_LDAP_URI )
 		end
 
+		it "can set the cacert file used for TLS" do
+			@conn.tls_cacertfile = Pathname( '/etc/openssl/cacerts/ldap.pem' )
+			@conn.tls_cacertfile.should == '/etc/openssl/cacerts/ldap.pem'
+		end
+
+		it "can set the cacert directory used for TLS" do
+			@conn.tls_cacertdir = Pathname( '/etc/openssl/cacerts' )
+			@conn.tls_cacertdir.should == '/etc/openssl/cacerts'
+		end
+
+		it "can set the certificate file used for TLS" do
+			@conn.tls_certfile = Pathname( '/etc/openssl/host.pem' )
+			@conn.tls_certfile.should == '/etc/openssl/host.pem'
+		end
+
+
+		it "can set the cipher suite used for TLS" do
+			@conn.tls_cipher_suite = 'HIGH:MEDIUM:+SSLv2'
+			@conn.tls_cipher_suite.should == 'HIGH:MEDIUM:+SSLv2'
+		end
+
+		it "can set the CRL checking strategy used for TLS" do
+			@conn.tls_crlcheck = :all
+			@conn.tls_crlcheck.should == :all
+		end
+
+		it "can set the CRL file used for TLS" do
+			@conn.tls_crlfile = '/var/db/036D5FF52BA395A08BA2F780F9ADEAD905431757.crl'
+			@conn.tls_crlfile.should == '/var/db/036D5FF52BA395A08BA2F780F9ADEAD905431757.crl'
+		end
+
+		it "can set the dhfile used for TLS" do
+			@conn.tls_dhfile = '/etc/openssl/dh1024.pem'
+			@conn.tls_dhfile.should == '/etc/openssl/dh1024.pem'
+		end
+
+		it "can set the keyfile used for TLS" do
+			@conn.tls_keyfile = '/etc/openssl/host.key'
+			@conn.tls_keyfile.should == '/etc/openssl/host.key'
+		end
+
+		it "can set the package used for TLS in recent versions of libldap" do
+			if OpenLDAP.api_info[:vendor_name] == "OpenLDAP" &&
+				OpenLDAP.api_info[:vendor_version] >= 20426
+				@conn.tls_package.should == 'OpenSSL'
+			else
+				expect {
+					@conn.tls_package
+				}.to raise_error( NotImplementedError, /version of libldap/i )
+			end
+		end
+
+		it "can set the minimum protocol version used for TLS" do
+			@conn.tls_protocol_min = 11
+			@conn.tls_protocol_min.should == 11
+		end
+
+		it "can set the random_file used for TLS" do
+			# :TODO: Don't know how to test this appropriately.
+			# @conn.tls_random_file = '/dev/urandom'
+			@conn.tls_random_file.should be_nil()
+		end
+
+		it "can set the certificate verification strategy used for TLS" do
+			@conn.tls_require_cert = :demand
+			@conn.tls_require_cert.should == :demand
+		end
+
+		it "can be set to time out if the connect(2) takes longer than 2 seconds" do
+			@conn.network_timeout = 2.0
+			@conn.network_timeout.should == 2.0
+		end
+
+		it "can have its network timeout reset" do
+			@conn.network_timeout = nil
+			@conn.network_timeout.should be_nil()
+		end
+
+		it "can connect asynchronously" do
+			@conn.async_connect?.should be_false()
+			@conn.async_connect = true
+			@conn.async_connect?.should be_true()
+		end
+
+		it "can disable asynchronous connections after they've been enabled" do
+			@conn.async_connect = true
+			@conn.async_connect = false
+			@conn.async_connect?.should be_false()
+		end
+
+	end
+
+	context "an unconnected instance" do
+
+		before( :each ) do
+			@conn = OpenLDAP::Connection.new( TEST_LDAP_URI )
+		end
+
+		it "knows that its file-descriptor is nil" do
+			@conn.fdno.should be_nil()
+		end
+
+	end
+
+
+	it "can start TLS negotiation synchronously", :with_ldap_server => true do
+		config = load_ldap_config()
+		uri = config['uri'] or abort "No 'uri' in the test config!"
+
+		conn = OpenLDAP::Connection.new( uri )
+		conn.start_tls
+	end
+
+	context "a connected instance", :with_ldap_server => true do
+
+		before( :all ) do
+			config = load_ldap_config()
+			@uri = config['uri'] or abort "No 'uri' in the test config!"
+		end
+
+		before( :each ) do
+			@conn = OpenLDAP::Connection.new( @uri )
+			@conn.start_tls
+		end
+
+		it "knows what its file-descriptor is" do
+			@conn.fdno.should be_a( Fixnum )
+			@conn.fdno.should > 2 # Something past STDERR
+		end
+
+		it "can return an IO for selecting against its file-descriptor" do
+			@conn.socket.should be_an( IO )
+			@conn.socket.fileno.should == @conn.fdno
+			@conn.socket.should_not be_autoclose()
+			@conn.socket.should_not be_close_on_exec()
+			@conn.socket.should be_binmode()
+		end
+
 	end
 
 end

File test.conf-example

+uri: ldap://ldap.example.com
+base: "dc=example,dc=com"