Commits

Michael Granger committed 42d35d5

More work on the toplevel patch

  • Participants
  • Parent commits ccc779e

Comments (0)

Files changed (2)

toplevel

-# HG changeset patch
-# Parent 031b7ced24141ca537d1a4cf9fd4d5543410ca9d
-Implementing top-level OpenLDAP functions
-
-diff --git a/Rakefile b/Rakefile
---- a/Rakefile
-+++ b/Rakefile
-@@ -50,8 +50,8 @@
- 
- 	self.require_ruby_version( '>= 1.8.7' )
- 
--	self.hg_sign_tags = true if self.plugin?( :mercurial )
--	self.yard_opts = [ '--protected', '--verbose' ] if self.plugin?( :yard )
-+	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
-diff --git a/ext/connection.c b/ext/connection.c
-new file mode 100644
---- /dev/null
-+++ b/ext/connection.c
-@@ -0,0 +1,202 @@
-+/*
-+ * Ruby-OpenLDAP -- a Ruby binding to OpenLDAP's libldap
-+ * $Id$
-+ *
-+ * Authors
-+ *
-+ * - Michael Granger <ged@FaerieMUD.org>
-+ *
-+ * Copyright (c) 2011 Michael Granger
-+ *
-+ * All rights reserved.
-+ *
-+ * Redistribution and use in source and binary forms, with or without modification, are
-+ * permitted provided that the following conditions are met:
-+ *
-+ *  * Redistributions of source code must retain the above copyright notice, this
-+ *    list of conditions and the following disclaimer.
-+ *
-+ *  * Redistributions in binary form must reproduce the above copyright notice, this
-+ *    list of conditions and the following disclaimer in the documentation and/or
-+ *    other materials provided with the distribution.
-+ *
-+ *  * Neither the name of the authors, nor the names of its contributors may be used to
-+ *    endorse or promote products derived from this software without specific prior
-+ *    written permission.
-+ *
-+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
-+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
-+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
-+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-+ *
-+ *
-+ */
-+
-+#include "openldap.h"
-+
-+
-+/* --------------------------------------------------------------
-+ * Declarations
-+ * -------------------------------------------------------------- */
-+VALUE ropenldap_cOpenLDAPConnection;
-+
-+
-+/* --------------------------------------------------
-+ *	Memory-management functions
-+ * -------------------------------------------------- */
-+
-+/*
-+ * Allocation function
-+ */
-+static struct ropenldap_connection *
-+ropenldap_conn_alloc( LDAP *ldp )
-+{
-+	struct ropenldap_connection *ptr = ALLOC( struct ropenldap_connection );
-+
-+	ptr->ldap = ldp;
-+
-+	return ptr;
-+}
-+
-+
-+/*
-+ * GC Mark function
-+ */
-+static void
-+ropenldap_conn_gc_mark( struct ropenldap_connection *ptr )
-+{
-+	/* No-op */
-+}
-+
-+
-+
-+/*
-+ * GC Free function
-+ */
-+static void
-+ropenldap_conn_gc_free( struct ropenldap_connection *ptr )
-+{
-+	if ( ptr ) {
-+		ptr->ldap = NULL;
-+
-+		xfree( ptr );
-+		ptr = NULL;
-+	}
-+}
-+
-+
-+/*
-+ * Object validity checker. Returns the data pointer.
-+ */
-+static struct ropenldap_connection *
-+check_conn( VALUE self )
-+{
-+	Check_Type( self, T_DATA );
-+
-+    if ( !IsConnection(self) ) {
-+		rb_raise( rb_eTypeError, "wrong argument type %s (expected an OpenLDAP::Connection)",
-+				  rb_obj_classname( self ) );
-+    }
-+
-+	return DATA_PTR( self );
-+}
-+
-+
-+/*
-+ * Fetch the data pointer and check it for sanity.
-+ */
-+static struct ropenldap_connection *
-+ropenldap_get_conn( VALUE self )
-+{
-+	struct ropenldap_connection *conn = check_conn( self );
-+
-+	if ( !conn ) rb_fatal( "Use of uninitialized OpenLDAP::Connection" );
-+
-+	return conn;
-+}
-+
-+
-+
-+/* --------------------------------------------------------------
-+ * Class methods
-+ * -------------------------------------------------------------- */
-+
-+/*
-+ *  call-seq:
-+ *     OpenLDAP::Connection.allocate   -> store
-+ *
-+ *  Allocate a new OpenLDAP::Connection object.
-+ *
-+ */
-+static VALUE
-+ropenldap_conn_s_allocate( VALUE klass ) {
-+	return Data_Wrap_Struct( klass, ropenldap_conn_gc_mark, ropenldap_conn_gc_free, 0 );
-+}
-+
-+
-+/* --------------------------------------------------------------
-+ * Instance methods
-+ * -------------------------------------------------------------- */
-+
-+/*
-+ *  call-seq:
-+ *     OpenLDAP::Connection.new( url )           -> conn
-+ *
-+ *  Create a new OpenLDAP::Connection object using the given +uri+.
-+ *
-+ */
-+static VALUE
-+ropenldap_conn_initialize( VALUE self, VALUE uristring ) {
-+	ropenldap_log_obj( self, "debug", "Initializing 0x%x", self );
-+
-+	if ( !check_conn(self) ) {
-+		LDAP *ldp = NULL;
-+		char *uri = StringValueCStr( uristring );
-+		struct ropenldap_connection *conn;
-+		int result = 0;
-+
-+		result = ldap_initialize( &ldp, uri );
-+		ropenldap_check_result( ldp, result );
-+		ropenldap_log_obj( self, "info", "Created a new %s (%s)", rb_obj_classname(self), uri );
-+
-+		conn = DATA_PTR( self ) = ropenldap_conn_alloc( ldp );
-+
-+	} else {
-+		rb_raise( ropenldap_eOpenLDAPError,
-+				  "Cannot re-initialize a store once it's been created." );
-+	}
-+
-+	return Qnil;
-+}
-+
-+
-+
-+
-+
-+/*
-+ * OpenLDAP Connection class
-+ */
-+void
-+ropenldap_init_connection( void ) {
-+	ropenldap_log( "debug", "Initializing OpenLDAP::Connection" );
-+
-+#ifdef FOR_RDOC
-+	ropenldap_mOpenLDAP = rb_define_module( "OpenLDAP" );
-+#endif
-+
-+	/* OpenLDAP::Connection */
-+	ropenldap_cOpenLDAPConnection =
-+		rb_define_class_under( ropenldap_mOpenLDAP, "Connection", rb_cObject );
-+	rb_define_alloc_func( ropenldap_cOpenLDAPConnection, ropenldap_conn_s_allocate );
-+
-+	rb_define_method( ropenldap_cOpenLDAPConnection, "initialize", ropenldap_conn_initialize, 1 );
-+
-+}
-+
-diff --git a/ext/extconf.rb b/ext/extconf.rb
---- a/ext/extconf.rb
-+++ b/ext/extconf.rb
-@@ -13,6 +13,8 @@
- 
- find_library( 'ldap', 'ldap_initialize' ) or
- 	abort( "Could not find LDAP library (http://openldap.org/)." )
--find_header( 'ldap.h' )  	or abort( "missing ldap.h" )
-+find_header( 'ldap.h' ) or abort( "missing ldap.h" )
-+
-+have_const( 'LDAP_API_VERSION', 'ldap.h' ) or abort "no LDAP_API_VERSION constant defined"
- 
- create_makefile( 'openldap_ext' )
-diff --git a/ext/openldap.c b/ext/openldap.c
---- a/ext/openldap.c
-+++ b/ext/openldap.c
-@@ -43,9 +43,303 @@
- 
- VALUE ropenldap_mOpenLDAP;
- 
-+VALUE ropenldap_eOpenLDAPError;
-+
-+
-+/* --------------------------------------------------------------
-+ * Logging Functions
-+ * -------------------------------------------------------------- */
-+
-+/*
-+ * Log a message to the given +context+ object's logger.
-+ */
-+void
-+#ifdef HAVE_STDARG_PROTOTYPES
-+ropenldap_log_obj( VALUE context, const char *level, const char *fmt, ... )
-+#else
-+ropenldap_log_obj( VALUE context, const char *level, const char *fmt, va_dcl )
-+#endif
-+{
-+	char buf[BUFSIZ];
-+	va_list	args;
-+	VALUE logger = Qnil;
-+	VALUE message = Qnil;
-+
-+	va_start( args, fmt );
-+	vsnprintf( buf, BUFSIZ, fmt, args );
-+	message = rb_str_new2( buf );
-+
-+	logger = rb_funcall( context, rb_intern("log"), 0, 0 );
-+	rb_funcall( logger, rb_intern(level), 1, message );
-+
-+	va_end( args );
-+}
-+
-+
-+/*
-+ * Log a message to the global logger.
-+ */
-+void
-+#ifdef HAVE_STDARG_PROTOTYPES
-+ropenldap_log( const char *level, const char *fmt, ... )
-+#else
-+ropenldap_log( const char *level, const char *fmt, va_dcl )
-+#endif
-+{
-+	char buf[BUFSIZ];
-+	va_list	args;
-+	VALUE logger = Qnil;
-+	VALUE message = Qnil;
-+
-+	va_init_list( args, fmt );
-+	vsnprintf( buf, BUFSIZ, fmt, args );
-+	message = rb_str_new2( buf );
-+
-+	logger = rb_funcall( ropenldap_mOpenLDAP, rb_intern("logger"), 0, 0 );
-+	rb_funcall( logger, rb_intern(level), 1, message );
-+
-+	va_end( args );
-+}
-+
-+
-+/* 
-+ * Raise an appropriate exception with an appropriate message for the given
-+ * resultcode.
-+ */
-+void
-+ropenldap_check_result( LDAP *ldp, int resultcode )
-+{
-+	if ( resultcode == LDAP_SUCCESS ) return;
-+
-+	
-+}
-+
-+
-+/*
-+ * Convert an array of string pointers to a Ruby Array of Strings.
-+ */
-+static VALUE
-+ropenldap_rb_string_array( char **strings )
-+{
-+	VALUE ary = rb_ary_new();
-+	char **iter;
-+
-+	/* If there aren't any pointers, just return the empty Array */
-+	if ( !strings ) return ary;
-+
-+	for ( iter = strings ; *iter != NULL ; iter++ ) {
-+		ropenldap_log( "debug", "  adding %s to string array", *iter );
-+		rb_ary_push( ary, rb_str_new2(*iter) );
-+	}
-+
-+	return ary;
-+}
-+
-+
-+/*
-+ * call-seq:
-+ *    OpenLDAP.split_url( str )   -> array
-+ *
-+ * Split an LDAP URL into an array of its parts:
-+ * - uri_scheme
-+ * - host
-+ * - port
-+ * - base
-+ * - attrs
-+ * - scope
-+ * - filter
-+ * - exts
-+ * - crit_exts
-+ */
-+static VALUE
-+ropenldap_s_split_url( VALUE _, VALUE urlstring )
-+{
-+	const char *url = StringValueCStr( urlstring );
-+	LDAPURLDesc *urldesc;
-+	VALUE rval = Qnil, obj = Qnil;
-+
-+	if ( !ldap_is_ldap_url(url) )
-+		rb_raise( rb_eArgError, "Not an LDAP URL." );
-+
-+	/* Parse the URL */
-+	if ( ldap_url_parse(url, &urldesc) != 0 )
-+		rb_raise( rb_eRuntimeError, "Error parsing %s as an LDAP URL!", url );
-+
-+	rval = rb_ary_new2( 9 );
-+
-+	/* Scheme */
-+	if ( urldesc->lud_scheme ) {
-+		ropenldap_log( "debug", "  parsed scheme: %s", urldesc->lud_scheme );
-+		obj = rb_str_new2( urldesc->lud_scheme );
-+		OBJ_INFECT( obj, urlstring );
-+		rb_ary_store( rval, 0L, obj );
-+	}
-+
-+	/* LDAP host to contact */
-+	if ( urldesc->lud_host ) {
-+		ropenldap_log( "debug", "  parsed host: %s", urldesc->lud_host );
-+		obj = rb_str_new2( urldesc->lud_host );
-+		OBJ_INFECT( obj, urlstring );
-+		rb_ary_store( rval, 1L, obj );
-+	}
-+
-+	/* Port */
-+	rb_ary_store( rval, 2L, INT2FIX(urldesc->lud_port) );
-+
-+	/* Base DN */
-+	if ( urldesc->lud_dn ) {
-+		ropenldap_log( "debug", "  parsed DN: %s", urldesc->lud_dn );
-+		obj = rb_str_new2( urldesc->lud_dn );
-+		OBJ_INFECT( obj, urlstring );
-+		rb_ary_store( rval, 3L, obj );
-+	}
-+
-+	/* Attributes */
-+	rb_ary_store( rval, 4L, ropenldap_rb_string_array(urldesc->lud_attrs) );
-+
-+	/* Numeric scope (LDAP_SCOPE_*) */
-+	rb_ary_store( rval, 5L, INT2FIX(urldesc->lud_scope) );
-+
-+	/* Filter */
-+	if ( urldesc->lud_filter ) {
-+		ropenldap_log( "debug", "  parsed filter: %s", urldesc->lud_filter );
-+		obj = rb_str_new2( urldesc->lud_filter );
-+		OBJ_INFECT( obj, urlstring );
-+		rb_ary_store( rval, 6L, obj );
-+	}
-+
-+	/* lists of LDAP extensions */
-+	rb_ary_store( rval, 7L, ropenldap_rb_string_array(urldesc->lud_exts) );
-+
-+	/* Critical extension/s flag */
-+	rb_ary_store( rval, 8L, urldesc->lud_crit_exts ? Qtrue : Qfalse );
-+
-+	ldap_free_urldesc( urldesc );
-+
-+	return rval;
-+}
-+
-+
-+/*
-+ * call-seq:
-+ *    OpenLDAP.err2string( resultcode )   -> string
-+ *
-+ * Return a short description of the +resultcode+ returned by routines in this library.
-+ *
-+ */
-+static VALUE
-+ropenldap_s_err2string( VALUE _, VALUE resultcode )
-+{
-+	int err = FIX2INT( resultcode );
-+	char *string = ldap_err2string( err );
-+
-+	return rb_str_new2( string );
-+}
-+
-+
-+
- void
- Init_openldap_ext( void )
- {
- 	ropenldap_mOpenLDAP = rb_define_module( "OpenLDAP" );
-+
-+	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) );
-+
-+	/* 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_SCOPE_ONELEVEL", INT2FIX(LDAP_SCOPE_ONELEVEL) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_SCOPE_ONE", INT2FIX(LDAP_SCOPE_ONE) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_SCOPE_SUBTREE", INT2FIX(LDAP_SCOPE_SUBTREE) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_SCOPE_SUB", INT2FIX(LDAP_SCOPE_SUB) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_SCOPE_SUBORDINATE", INT2FIX(LDAP_SCOPE_SUBORDINATE) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_SCOPE_CHILDREN", INT2FIX(LDAP_SCOPE_CHILDREN) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_SCOPE_DEFAULT", INT2FIX(LDAP_SCOPE_DEFAULT) );
-+
-+	/* result codes */
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_SUCCESS", INT2FIX(LDAP_SUCCESS) );
-+
-+ 	rb_define_const( ropenldap_mOpenLDAP, "LDAP_OPERATIONS_ERROR", INT2FIX(LDAP_OPERATIONS_ERROR) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_PROTOCOL_ERROR", INT2FIX(LDAP_PROTOCOL_ERROR) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_TIMELIMIT_EXCEEDED", INT2FIX(LDAP_TIMELIMIT_EXCEEDED) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_SIZELIMIT_EXCEEDED", INT2FIX(LDAP_SIZELIMIT_EXCEEDED) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_COMPARE_FALSE", INT2FIX(LDAP_COMPARE_FALSE) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_COMPARE_TRUE", INT2FIX(LDAP_COMPARE_TRUE) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_AUTH_METHOD_NOT_SUPPORTED", INT2FIX(LDAP_AUTH_METHOD_NOT_SUPPORTED) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_STRONG_AUTH_NOT_SUPPORTED", INT2FIX(LDAP_STRONG_AUTH_NOT_SUPPORTED) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_STRONG_AUTH_REQUIRED", INT2FIX(LDAP_STRONG_AUTH_REQUIRED) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_STRONGER_AUTH_REQUIRED", INT2FIX(LDAP_STRONGER_AUTH_REQUIRED) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_PARTIAL_RESULTS", INT2FIX(LDAP_PARTIAL_RESULTS) );
-+
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_REFERRAL", INT2FIX(LDAP_REFERRAL) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_ADMINLIMIT_EXCEEDED", INT2FIX(LDAP_ADMINLIMIT_EXCEEDED) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_UNAVAILABLE_CRITICAL_EXTENSION", INT2FIX(LDAP_UNAVAILABLE_CRITICAL_EXTENSION) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_CONFIDENTIALITY_REQUIRED", INT2FIX(LDAP_CONFIDENTIALITY_REQUIRED) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_SASL_BIND_IN_PROGRESS", INT2FIX(LDAP_SASL_BIND_IN_PROGRESS) );
-+
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_NO_SUCH_ATTRIBUTE", INT2FIX(LDAP_NO_SUCH_ATTRIBUTE) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_UNDEFINED_TYPE", INT2FIX(LDAP_UNDEFINED_TYPE) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_INAPPROPRIATE_MATCHING", INT2FIX(LDAP_INAPPROPRIATE_MATCHING) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_CONSTRAINT_VIOLATION", INT2FIX(LDAP_CONSTRAINT_VIOLATION) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_TYPE_OR_VALUE_EXISTS", INT2FIX(LDAP_TYPE_OR_VALUE_EXISTS) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_INVALID_SYNTAX", INT2FIX(LDAP_INVALID_SYNTAX) );
-+
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_NO_SUCH_OBJECT", INT2FIX(LDAP_NO_SUCH_OBJECT) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_ALIAS_PROBLEM", INT2FIX(LDAP_ALIAS_PROBLEM) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_INVALID_DN_SYNTAX", INT2FIX(LDAP_INVALID_DN_SYNTAX) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_IS_LEAF", INT2FIX(LDAP_IS_LEAF) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_ALIAS_DEREF_PROBLEM", INT2FIX(LDAP_ALIAS_DEREF_PROBLEM) );
-+
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_X_PROXY_AUTHZ_FAILURE", INT2FIX(LDAP_X_PROXY_AUTHZ_FAILURE) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_INAPPROPRIATE_AUTH", INT2FIX(LDAP_INAPPROPRIATE_AUTH) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_INVALID_CREDENTIALS", INT2FIX(LDAP_INVALID_CREDENTIALS) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_INSUFFICIENT_ACCESS", INT2FIX(LDAP_INSUFFICIENT_ACCESS) );
-+
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_BUSY", INT2FIX(LDAP_BUSY) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_UNAVAILABLE", INT2FIX(LDAP_UNAVAILABLE) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_UNWILLING_TO_PERFORM", INT2FIX(LDAP_UNWILLING_TO_PERFORM) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_LOOP_DETECT", INT2FIX(LDAP_LOOP_DETECT) );
-+
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_NAMING_VIOLATION", INT2FIX(LDAP_NAMING_VIOLATION) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_OBJECT_CLASS_VIOLATION", INT2FIX(LDAP_OBJECT_CLASS_VIOLATION) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_NOT_ALLOWED_ON_NONLEAF", INT2FIX(LDAP_NOT_ALLOWED_ON_NONLEAF) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_NOT_ALLOWED_ON_RDN", INT2FIX(LDAP_NOT_ALLOWED_ON_RDN) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_ALREADY_EXISTS", INT2FIX(LDAP_ALREADY_EXISTS) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_NO_OBJECT_CLASS_MODS", INT2FIX(LDAP_NO_OBJECT_CLASS_MODS) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_RESULTS_TOO_LARGE", INT2FIX(LDAP_RESULTS_TOO_LARGE) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_AFFECTS_MULTIPLE_DSAS", INT2FIX(LDAP_AFFECTS_MULTIPLE_DSAS) );
-+
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_VLV_ERROR", INT2FIX(LDAP_VLV_ERROR) );
-+
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_OTHER", INT2FIX(LDAP_OTHER) );
-+
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_SERVER_DOWN", INT2FIX(LDAP_SERVER_DOWN) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_LOCAL_ERROR", INT2FIX(LDAP_LOCAL_ERROR) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_ENCODING_ERROR", INT2FIX(LDAP_ENCODING_ERROR) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_DECODING_ERROR", INT2FIX(LDAP_DECODING_ERROR) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_TIMEOUT", INT2FIX(LDAP_TIMEOUT) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_AUTH_UNKNOWN", INT2FIX(LDAP_AUTH_UNKNOWN) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_FILTER_ERROR", INT2FIX(LDAP_FILTER_ERROR) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_USER_CANCELLED", INT2FIX(LDAP_USER_CANCELLED) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_PARAM_ERROR", INT2FIX(LDAP_PARAM_ERROR) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_NO_MEMORY", INT2FIX(LDAP_NO_MEMORY) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_CONNECT_ERROR", INT2FIX(LDAP_CONNECT_ERROR) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_NOT_SUPPORTED", INT2FIX(LDAP_NOT_SUPPORTED) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_CONTROL_NOT_FOUND", INT2FIX(LDAP_CONTROL_NOT_FOUND) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_NO_RESULTS_RETURNED", INT2FIX(LDAP_NO_RESULTS_RETURNED) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_MORE_RESULTS_TO_RETURN", INT2FIX(LDAP_MORE_RESULTS_TO_RETURN) );
-+	rb_define_const( ropenldap_mOpenLDAP, "LDAP_CLIENT_LOOP", INT2FIX(LDAP_CLIENT_LOOP) );
-+	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) );
-+
-+	/* 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 );
-+
- }
- 
-diff --git a/ext/openldap.h b/ext/openldap.h
---- a/ext/openldap.h
-+++ b/ext/openldap.h
-@@ -8,6 +8,7 @@
- #include <stdio.h>
- #include <string.h>
- #include <inttypes.h>
-+#include <assert.h>
- 
- #include <ldap.h>
- 
-@@ -22,6 +23,8 @@
- 
- extern VALUE ropenldap_cOpenLDAPConnection;
- 
-+extern VALUE ropenldap_eOpenLDAPError;
-+
- 
- /* --------------------------------------------------------------
-  * Typedefs
-@@ -44,12 +47,27 @@
-  * Declarations
-  * -------------------------------------------------------------- */
- 
-+#ifdef HAVE_STDARG_PROTOTYPES
-+#include <stdarg.h>
-+#define va_init_list(a,b) va_start(a,b)
-+void ropenldap_log_obj( VALUE, const char *, const char *, ... );
-+void ropenldap_log( const char *, const char *, ... );
-+#else
-+#include <varargs.h>
-+#define va_init_list(a,b) va_start(a)
-+void ropenldap_log_obj( VALUE, const char *, const char *, va_dcl );
-+void ropenldap_log( const char *, const char *, va_dcl );
-+#endif
-+
-+void ropenldap_check_result				_(( LDAP *, int ));
-+
- 
- /* --------------------------------------------------------------
-  * Initializers
-  * -------------------------------------------------------------- */
- 
--void Init_openldap_ext( void );
-+void Init_openldap_ext					_(( void ));
-+void ropenldap_init_connection			_(( void ));
- 
- 
- #endif /* __OPENLDAP_H__ */
-diff --git a/lib/openldap.rb b/lib/openldap.rb
---- a/lib/openldap.rb
-+++ b/lib/openldap.rb
-@@ -12,7 +12,68 @@
- 	# Version-control revision constant
- 	REVISION = %q$Revision$
- 
-+	require 'openldap/utils'
- 
-+	### Logging
-+	# Log levels
-+	LOG_LEVELS = {
-+		'debug' => Logger::DEBUG,
-+		'info'  => Logger::INFO,
-+		'warn'  => Logger::WARN,
-+		'error' => Logger::ERROR,
-+		'fatal' => Logger::FATAL,
-+	}.freeze
-+	LOG_LEVEL_NAMES = LOG_LEVELS.invert.freeze
-+
-+	@default_logger = Logger.new( $stderr )
-+	@default_logger.level = $DEBUG ? Logger::DEBUG : Logger::WARN
-+
-+	@default_log_formatter = OpenLDAP::LogFormatter.new( @default_logger )
-+	@default_logger.formatter = @default_log_formatter
-+
-+	@logger = @default_logger
-+
-+
-+	class << self
-+		# @return [Logger::Formatter] the log formatter that will be used when the logging 
-+		#    subsystem is reset
-+		attr_accessor :default_log_formatter
-+
-+		# @return [Logger] the logger that will be used when the logging subsystem is reset
-+		attr_accessor :default_logger
-+
-+		# @return [Logger] the logger that's currently in effect
-+		attr_accessor :logger
-+		alias_method :log, :logger
-+		alias_method :log=, :logger=
-+	end
-+
-+
-+	### Reset the global logger object to the default
-+	### @return [void]
-+	def self::reset_logger
-+		self.logger = self.default_logger
-+		self.logger.level = Logger::WARN
-+		self.logger.formatter = self.default_log_formatter
-+	end
-+
-+
-+	### Returns +true+ if the global logger has not been set to something other than
-+	### the default one.
-+	def self::using_default_logger?
-+		return self.logger == self.default_logger
-+	end
-+
-+
-+	### Get the library version.
-+	### @return [String] the library's version
-+	def self::version_string( include_buildnum=false )
-+		vstring = "%s %s" % [ self.name, VERSION ]
-+		vstring << " (build %s)" % [ REVISION[/: ([[:xdigit:]]+)/, 1] || '0' ] if include_buildnum
-+		return vstring
-+	end
-+
-+	### Load the extension
- 	begin
- 		require 'openldap_ext'
- 	rescue LoadError => err
-@@ -27,5 +88,8 @@
- 
- 	end
- 
-+	# Load the Ruby parts of the library
-+	require 'openldap/exceptions'
-+
- end # module OpenLDAP
- 
-diff --git a/lib/openldap/exceptions.rb b/lib/openldap/exceptions.rb
-new file mode 100644
---- /dev/null
-+++ b/lib/openldap/exceptions.rb
-@@ -0,0 +1,159 @@
-+#!/usr/bin/env ruby
-+
-+require 'openldap' unless defined?( OpenLDAP )
-+
-+
-+module OpenLDAP
-+
-+	# A map of result codes to the corresponding exception class
-+	RESULT_EXCEPTION_CLASS = {}
-+
-+	# The base class for all OpenLDAP exceptions
-+	class Error < RuntimeError
-+
-+		# The result code that corresponds to the exception type
-+		@result_code = nil
-+		class << self; attr_accessor :result_code; end
-+
-+		### Inheritance hook -- Initialize the result code class instance variable
-+		### for inheriting exception classes.
-+		def self::inherited( subclass )
-+			subclass.instance_variable_set( :@result_code, nil )
-+		end
-+
-+	end # class Error
-+
-+
-+	### Define a new Exception class named +classname+ for the specified +result_code+
-+	### and inheriting from +superclass+.
-+	def self::def_ldap_exception( classname, result_code, superclass=OpenLDAP::Error )
-+		eclass = Class.new( superclass ) do
-+			def initialize( message=nil )
-+				ldapmsg = OpenLDAP.err2string( self.class.result_code )
-+				ldapmsg += ': ' + message if message
-+				super( ldapmsg )
-+			end
-+		end
-+		eclass.result_code = result_code
-+
-+		const_set( classname, eclass )
-+		RESULT_EXCEPTION_CLASS[ result_code ] = eclass
-+	end
-+
-+
-+	# The LDAP referral class -- raised when the target LDAP directory instructs
-+	# the client to refer to another directory
-+	class Referral < OpenLDAP::Error
-+
-+		### Create a new referral to the specified +url+.
-+		def initialize( url )
-+			super( "Referral to #{url}" )
-+			@url = url
-+		end
-+
-+		######
-+		public
-+		######
-+
-+		# The URL of the directory to refer to
-+		attr_reader :url
-+
-+	end # class Referral
-+
-+
-+	def_ldap_exception :OperationsError, LDAP_OPERATIONS_ERROR
-+	def_ldap_exception :ProtocolError, LDAP_PROTOCOL_ERROR
-+	def_ldap_exception :TimelimitExceeded, LDAP_TIMELIMIT_EXCEEDED
-+	def_ldap_exception :SizelimitExceeded, LDAP_SIZELIMIT_EXCEEDED
-+	def_ldap_exception :CompareFalse, LDAP_COMPARE_FALSE
-+	def_ldap_exception :CompareTrue, LDAP_COMPARE_TRUE
-+	def_ldap_exception :AuthMethodNotSupported, LDAP_AUTH_METHOD_NOT_SUPPORTED
-+	def_ldap_exception :StrongAuthRequired, LDAP_STRONG_AUTH_REQUIRED
-+	def_ldap_exception :PartialResults, LDAP_PARTIAL_RESULTS
-+	def_ldap_exception :AdminlimitExceeded, LDAP_ADMINLIMIT_EXCEEDED
-+	def_ldap_exception :UnavailableCriticalExtension, LDAP_UNAVAILABLE_CRITICAL_EXTENSION
-+	def_ldap_exception :ConfidentialityRequired, LDAP_CONFIDENTIALITY_REQUIRED
-+	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
-+
-+	def_ldap_exception :NoSuchAttribute, LDAP_NO_SUCH_ATTRIBUTE, OpenLDAP::AttrError
-+	def_ldap_exception :UndefinedType, LDAP_UNDEFINED_TYPE, OpenLDAP::AttrError
-+	def_ldap_exception :InappropriateMatching, LDAP_INAPPROPRIATE_MATCHING, OpenLDAP::AttrError
-+	def_ldap_exception :ConstraintViolation, LDAP_CONSTRAINT_VIOLATION, OpenLDAP::AttrError
-+	def_ldap_exception :TypeOrValueExists, LDAP_TYPE_OR_VALUE_EXISTS, 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
-+
-+	def_ldap_exception :NoSuchObject, LDAP_NO_SUCH_OBJECT, OpenLDAP::NameError
-+	def_ldap_exception :AliasProblem, LDAP_ALIAS_PROBLEM, OpenLDAP::NameError
-+	def_ldap_exception :InvalidDNSyntax, LDAP_INVALID_DN_SYNTAX, OpenLDAP::NameError
-+	def_ldap_exception :IsLeaf, LDAP_IS_LEAF, 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
-+
-+	def_ldap_exception :XProxyAuthzFailure, LDAP_X_PROXY_AUTHZ_FAILURE, OpenLDAP::SecurityError
-+	def_ldap_exception :InappropriateAuth, LDAP_INAPPROPRIATE_AUTH, OpenLDAP::SecurityError
-+	def_ldap_exception :InvalidCredentials, LDAP_INVALID_CREDENTIALS, 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
-+
-+	def_ldap_exception :Busy, LDAP_BUSY, OpenLDAP::ServiceError
-+	def_ldap_exception :Unavailable, LDAP_UNAVAILABLE, OpenLDAP::ServiceError
-+	def_ldap_exception :UnwillingToPerform, LDAP_UNWILLING_TO_PERFORM, 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
-+
-+	def_ldap_exception :NamingViolation, LDAP_NAMING_VIOLATION, OpenLDAP::UpdateError
-+	def_ldap_exception :ObjectClassViolation, LDAP_OBJECT_CLASS_VIOLATION, OpenLDAP::UpdateError
-+	def_ldap_exception :NotAllowedOnNonleaf, LDAP_NOT_ALLOWED_ON_NONLEAF, OpenLDAP::UpdateError
-+	def_ldap_exception :NotAllowedOnRdn, LDAP_NOT_ALLOWED_ON_RDN, OpenLDAP::UpdateError
-+	def_ldap_exception :AlreadyExists, LDAP_ALREADY_EXISTS, OpenLDAP::UpdateError
-+	def_ldap_exception :NoObjectClassMods, LDAP_NO_OBJECT_CLASS_MODS, OpenLDAP::UpdateError
-+	def_ldap_exception :ResultsTooLarge, LDAP_RESULTS_TOO_LARGE, OpenLDAP::UpdateError
-+	def_ldap_exception :AffectsMultipleDSAs, LDAP_AFFECTS_MULTIPLE_DSAS, OpenLDAP::UpdateError
-+
-+	def_ldap_exception :VLVError, LDAP_VLV_ERROR
-+
-+	# Implementation-specific errors
-+	class OtherError < OpenLDAP::Error; end
-+
-+	# API Error Codes
-+	# 
-+	# Based on draft-ietf-ldap-c-api-xx
-+	# but with new negative code values
-+	# 
-+	class APIError < OpenLDAP::Error; end
-+
-+	def_ldap_exception :ServerDown, LDAP_SERVER_DOWN, OpenLDAP::APIError
-+	def_ldap_exception :LocalError, LDAP_LOCAL_ERROR, OpenLDAP::APIError
-+	def_ldap_exception :EncodingError, LDAP_ENCODING_ERROR, OpenLDAP::APIError
-+	def_ldap_exception :DecodingError, LDAP_DECODING_ERROR, OpenLDAP::APIError
-+	def_ldap_exception :Timeout, LDAP_TIMEOUT, OpenLDAP::APIError
-+	def_ldap_exception :AuthUnknown, LDAP_AUTH_UNKNOWN, OpenLDAP::APIError
-+	def_ldap_exception :FilterError, LDAP_FILTER_ERROR, OpenLDAP::APIError
-+	def_ldap_exception :UserCancelled, LDAP_USER_CANCELLED, OpenLDAP::APIError
-+	def_ldap_exception :ParamError, LDAP_PARAM_ERROR, OpenLDAP::APIError
-+	def_ldap_exception :NoMemory, LDAP_NO_MEMORY, OpenLDAP::APIError
-+	def_ldap_exception :ConnectError, LDAP_CONNECT_ERROR, OpenLDAP::APIError
-+	def_ldap_exception :NotSupported, LDAP_NOT_SUPPORTED, OpenLDAP::APIError
-+	def_ldap_exception :ControlNotFound, LDAP_CONTROL_NOT_FOUND, OpenLDAP::APIError
-+	def_ldap_exception :NoResultsReturned, LDAP_NO_RESULTS_RETURNED, OpenLDAP::APIError
-+	def_ldap_exception :MoreResultsToReturn, LDAP_MORE_RESULTS_TO_RETURN, OpenLDAP::APIError
-+	def_ldap_exception :ClientLoop, LDAP_CLIENT_LOOP, OpenLDAP::APIError
-+	def_ldap_exception :ReferralLimitExceeded, LDAP_REFERRAL_LIMIT_EXCEEDED, OpenLDAP::APIError
-+	def_ldap_exception :XConnecting, LDAP_X_CONNECTING, OpenLDAP::APIError
-+
-+
-+end # module OpenLDAP
-+
-+
-diff --git a/lib/openldap/mixins.rb b/lib/openldap/mixins.rb
-new file mode 100644
---- /dev/null
-+++ b/lib/openldap/mixins.rb
-@@ -0,0 +1,65 @@
-+#!/usr/bin/ruby
-+
-+require 'rbconfig'
-+require 'erb'
-+require 'etc'
-+require 'logger'
-+
-+require 'openldap'
-+
-+
-+#--
-+# A collection of mixins shared between OpenLDAP classes. Stolen mostly from ThingFish.
-+#
-+module OpenLDAP # :nodoc:
-+
-+	# Add logging to a OpenLDAP class. Including classes get #log and #log_debug methods.
-+	module Loggable
-+
-+		LEVEL = {
-+			:debug => Logger::DEBUG,
-+			:info  => Logger::INFO,
-+			:warn  => Logger::WARN,
-+			:error => Logger::ERROR,
-+			:fatal => Logger::FATAL,
-+		  }
-+
-+		### A logging proxy class that wraps calls to the logger into calls that include
-+		### the name of the calling class.
-+		class ClassNameProxy # :nodoc:
-+
-+			### Create a new proxy for the given +klass+.
-+			def initialize( klass, force_debug=false )
-+				@classname   = klass.name
-+				@force_debug = force_debug
-+			end
-+
-+			### Delegate calls the global logger with the class name as the 'progname' 
-+			### argument.
-+			def method_missing( sym, msg=nil, &block )
-+				return super unless LEVEL.key?( sym )
-+				sym = :debug if @force_debug
-+				OpenLDAP.logger.add( LEVEL[sym], msg, @classname, &block )
-+			end
-+		end # ClassNameProxy
-+
-+		#########
-+		protected
-+		#########
-+
-+		### Return the proxied logger.
-+		def log
-+			@log_proxy ||= ClassNameProxy.new( self.class )
-+		end
-+
-+		### Return a proxied "debug" logger that ignores other level specification.
-+		def log_debug
-+			@log_debug_proxy ||= ClassNameProxy.new( self.class, true )
-+		end
-+	end # module Loggable
-+
-+
-+end # module OpenLDAP
-+
-+# vim: set nosta noet ts=4 sw=4:
-+
-diff --git a/lib/openldap/utils.rb b/lib/openldap/utils.rb
-new file mode 100644
---- /dev/null
-+++ b/lib/openldap/utils.rb
-@@ -0,0 +1,123 @@
-+#!/usr/bin/env ruby
-+
-+require 'logger'
-+require 'erb'
-+
-+require 'openldap' unless defined?( OpenLDAP )
-+
-+module OpenLDAP # :nodoc:
-+
-+	# A alternate formatter for Logger instances.
-+	class LogFormatter < Logger::Formatter
-+
-+		# The format to output unless debugging is turned on
-+		DEFAULT_FORMAT = "[%1$s.%2$06d %3$d/%4$s] %5$5s -- %7$s\n"
-+
-+		# The format to output if debugging is turned on
-+		DEFAULT_DEBUG_FORMAT = "[%1$s.%2$06d %3$d/%4$s] %5$5s {%6$s} -- %7$s\n"
-+
-+
-+		### Initialize the formatter with a reference to the logger so it can check for log level.
-+		def initialize( logger, format=DEFAULT_FORMAT, debug=DEFAULT_DEBUG_FORMAT ) # :notnew:
-+			@logger       = logger
-+			@format       = format
-+			@debug_format = debug
-+
-+			super()
-+		end
-+
-+		######
-+		public
-+		######
-+
-+		# The Logger object associated with the formatter
-+		attr_accessor :logger
-+
-+		# The logging format string
-+		attr_accessor :format
-+
-+		# The logging format string that's used when outputting in debug mode
-+		attr_accessor :debug_format
-+
-+
-+		### Log using either the DEBUG_FORMAT if the associated logger is at ::DEBUG level or
-+		### using FORMAT if it's anything less verbose.
-+		def call( severity, time, progname, msg )
-+			args = [
-+				time.strftime( '%Y-%m-%d %H:%M:%S' ),                         # %1$s
-+				time.usec,                                                    # %2$d
-+				Process.pid,                                                  # %3$d
-+				Thread.current == Thread.main ? 'main' : Thread.object_id,    # %4$s
-+				severity,                                                     # %5$s
-+				progname,                                                     # %6$s
-+				msg                                                           # %7$s
-+			]
-+
-+			if @logger.level == Logger::DEBUG
-+				return self.debug_format % args
-+			else
-+				return self.format % args
-+			end
-+		end
-+	end # class LogFormatter
-+
-+
-+	# An alternate formatter for Logger instances that outputs +dd+ HTML
-+	# fragments.
-+	class HtmlLogFormatter < Logger::Formatter
-+		include ERB::Util  # for html_escape()
-+
-+		# The default HTML fragment that'll be used as the template for each log message.
-+		HTML_LOG_FORMAT = %q{
-+		<div class="log-message %5$s">
-+			<span class="log-time">%1$s.%2$06d</span>
-+			[
-+				<span class="log-pid">%3$d</span>
-+				/
-+				<span class="log-tid">%4$s</span>
-+			]
-+			<span class="log-level">%5$s</span>
-+			:
-+			<span class="log-name">%6$s</span>
-+			<span class="log-message-text">%7$s</span>
-+		</div>
-+		}
-+
-+		### Override the logging formats with ones that generate HTML fragments
-+		def initialize( logger, format=HTML_LOG_FORMAT ) # :notnew:
-+			@logger = logger
-+			@format = format
-+			super()
-+		end
-+
-+
-+		######
-+		public
-+		######
-+
-+		# The HTML fragment that will be used as a format() string for the log
-+		attr_accessor :format
-+
-+
-+		### Return a log message composed out of the arguments formatted using the
-+		### formatter's format string
-+		def call( severity, time, progname, msg )
-+			args = [
-+				time.strftime( '%Y-%m-%d %H:%M:%S' ),                         # %1$s
-+				time.usec,                                                    # %2$d
-+				Process.pid,                                                  # %3$d
-+				Thread.current == Thread.main ? 'main' : Thread.object_id,    # %4$s
-+				severity.downcase,                                                     # %5$s
-+				progname,                                                     # %6$s
-+				html_escape( msg ).gsub(/\n/, '<br />')                       # %7$s
-+			]
-+
-+			return self.format % args
-+		end
-+
-+	end # class HtmlLogFormatter
-+
-+end # module OpenLDAP
-+
-+# vim: set nosta noet ts=4 sw=4:
-+
-diff --git a/spec/lib/constants.rb b/spec/lib/constants.rb
-new file mode 100644
---- /dev/null
-+++ b/spec/lib/constants.rb
-@@ -0,0 +1,20 @@
-+#!/usr/bin/env ruby
-+
-+require 'pathname'
-+
-+require 'openldap'
-+
-+
-+module OpenLDAP::TestConstants
-+
-+	unless defined?( TEST_LDAP_URI )
-+
-+		TEST_LDAP_URI = 'ldap://ldap.acme.com/dc=acme,dc=com'
-+
-+		constants.each do |cname|
-+			const_get(cname).freeze
-+		end
-+	end
-+
-+end
-+
-diff --git a/spec/lib/helpers.rb b/spec/lib/helpers.rb
-new file mode 100644
---- /dev/null
-+++ b/spec/lib/helpers.rb
-@@ -0,0 +1,112 @@
-+#!/usr/bin/ruby
-+# coding: utf-8
-+
-+BEGIN {
-+	require 'rbconfig'
-+	require 'pathname'
-+	basedir = Pathname.new( __FILE__ ).dirname.parent
-+
-+	libdir = basedir + "lib"
-+	extdir = libdir + Config::CONFIG['sitearch']
-+
-+	$LOAD_PATH.unshift( libdir.to_s ) unless $LOAD_PATH.include?( libdir.to_s )
-+	$LOAD_PATH.unshift( extdir.to_s ) unless $LOAD_PATH.include?( extdir.to_s )
-+}
-+
-+require 'pp'
-+require 'yaml'
-+require 'logger'
-+
-+require 'openldap'
-+require 'openldap/mixins'
-+
-+require 'spec/lib/constants'
-+
-+### Return a string-comparable version vector from +version+.
-+def vvec( version )
-+	return version.split( '.' ).map( &:to_i ).pack( 'N' )
-+end
-+
-+
-+### RSpec helper functions.
-+module OpenLDAP::SpecHelpers
-+	include OpenLDAP::TestConstants
-+
-+	class ArrayLogger
-+		### Create a new ArrayLogger that will append content to +array+.
-+		def initialize( array )
-+			@array = array
-+		end
-+
-+		### Write the specified +message+ to the array.
-+		def write( message )
-+			@array << message
-+		end
-+
-+		### No-op -- this is here just so Logger doesn't complain
-+		def close; end
-+
-+	end # class ArrayLogger
-+
-+
-+	unless defined?( LEVEL )
-+		LEVEL = {
-+			:debug => Logger::DEBUG,
-+			:info  => Logger::INFO,
-+			:warn  => Logger::WARN,
-+			:error => Logger::ERROR,
-+			:fatal => Logger::FATAL,
-+		  }
-+	end
-+
-+
-+	###############
-+	module_function
-+	###############
-+
-+	### Reset the logging subsystem to its default state.
-+	def reset_logging
-+		OpenLDAP.reset_logger
-+	end
-+
-+
-+	### Alter the output of the default log formatter to be pretty in SpecMate output
-+	def setup_logging( level=Logger::FATAL )
-+
-+		# Turn symbol-style level config into Logger's expected Fixnum level
-+		if OpenLDAP::Loggable::LEVEL.key?( level )
-+			level = OpenLDAP::Loggable::LEVEL[ level ]
-+		end
-+
-+		logger = Logger.new( $stderr )
-+		OpenLDAP.logger = logger
-+		OpenLDAP.logger.level = level
-+
-+		# Only do this when executing from a spec in TextMate
-+		if ENV['HTML_LOGGING'] || (ENV['TM_FILENAME'] && ENV['TM_FILENAME'] =~ /_spec\.rb/)
-+			Thread.current['logger-output'] = []
-+			logdevice = ArrayLogger.new( Thread.current['logger-output'] )
-+			OpenLDAP.logger = Logger.new( logdevice )
-+			# OpenLDAP.logger.level = level
-+			OpenLDAP.logger.formatter = OpenLDAP::HtmlLogFormatter.new( OpenLDAP.logger )
-+		end
-+	end
-+
-+
-+end
-+
-+
-+### Mock with Rspec
-+Rspec.configure do |config|
-+	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' )
-+end
-+
-+# vim: set nosta noet ts=4 sw=4:
-+
-diff --git a/spec/openldap/connection_spec.rb b/spec/openldap/connection_spec.rb
-new file mode 100644
---- /dev/null
-+++ b/spec/openldap/connection_spec.rb
-@@ -0,0 +1,35 @@
-+#!/usr/bin/env rspec -cfd -b
-+
-+BEGIN {
-+	require 'pathname'
-+	basedir = Pathname( __FILE__ ).dirname.parent.parent
-+	libdir = basedir + 'lib'
-+
-+	$LOAD_PATH.unshift( basedir.to_s ) unless $LOAD_PATH.include?( basedir.to_s )
-+	$LOAD_PATH.unshift( libdir.to_s ) unless $LOAD_PATH.include?( libdir.to_s )
-+}
-+
-+require 'rspec'
-+require 'spec/lib/helpers'
-+require 'openldap/connection'
-+
-+describe OpenLDAP::Connection do
-+
-+	before( :all ) do
-+		setup_logging( :debug )
-+	end
-+
-+	after( :all ) do
-+		reset_logging()
-+	end
-+
-+
-+	TEST_LDAP_URI = 'ldap://localhost/dc=acme,dc=com'
-+
-+	it "can be created with an LDAP uri" do
-+		OpenLDAP::Connection.new( TEST_LDAP_URI )
-+	end
-+
-+
-+end
-+
-diff --git a/spec/openldap_spec.rb b/spec/openldap_spec.rb
---- a/spec/openldap_spec.rb
-+++ b/spec/openldap_spec.rb
-@@ -5,16 +5,67 @@
- 	basedir = Pathname( __FILE__ ).dirname.parent
- 	libdir = basedir + 'lib'
- 
-+	$LOAD_PATH.unshift( basedir.to_s ) unless $LOAD_PATH.include?( basedir.to_s )
- 	$LOAD_PATH.unshift( libdir.to_s ) unless $LOAD_PATH.include?( libdir.to_s )
- }
- 
- require 'rspec'
-+require 'spec/lib/helpers'
- require 'openldap'
- 
- describe OpenLDAP do
- 
--	it "is well-tested" do
--		fail "it isn't"
-+	before( :all ) do
-+		setup_logging( :debug )
-+	end
-+
-+	after( :all ) do
-+		reset_logging()
-+	end
-+
-+
-+    # typedef struct ldap_url_desc {
-+    #     char *      lud_scheme;     /* URI scheme */
-+    #     char *      lud_host;       /* LDAP host to contact */
-+    #     int         lud_port;       /* port on host */
-+    #     char *      lud_dn;         /* base for search */
-+    #     char **     lud_attrs;      /* list of attributes */
-+    #     int         lud_scope;      /* a LDAP_SCOPE_... value */
-+    #     char *      lud_filter;     /* LDAP search filter */
-+    #     char **     lud_exts;       /* LDAP extensions */
-+    #     int         lud_crit_exts;  /* true if any extension is critical */
-+    #     /* may contain additional fields for internal use */
-+    # } LDAPURLDesc;
-+	it "can split an LDAP URL into its components" do
-+		OpenLDAP.split_url( 'ldap://ldap.acme.com/dc=acme,dc=com' ).should == [
-+			'ldap',
-+			'ldap.acme.com',
-+			389,
-+			'dc=acme,dc=com',
-+			[],
-+			OpenLDAP::LDAP_SCOPE_BASE,
-+			nil,
-+			[],
-+			false,
-+		]
-+	end
-+
-+	it "raises an argument error when asked to split a String that isn't an LDAP URL" do
-+		expect {
-+			OpenLDAP.split_url( 'your cat is incredibly soft' )
-+		}.to raise_error( ArgumentError, /not an ldap url/i )
-+	end
-+
-+	it "propagates taintedness from a split URL to its parts" do
-+		url = 'ldap://ldap.acme.com/dc=acme,dc=com'
-+		url.taint
-+
-+		result = OpenLDAP.split_url( url )
-+
-+		result[0].should be_tainted()
-+		result[1].should be_tainted()
-+		# port is an immediate object, so it's not tainted
-+		result[3].should be_tainted()
- 	end
- 
- end