Ldap C API mit ruby FFI anbinden
Auf der Suche nach einer geeigneten Ldap API für ruby bin ich auf treequel gestoßen. Treequel bietet eine schöne Möglichkeit Ldap Repositories zu durchsuchen – eben ganz ruby Style.
Bei der Installation des Gems kam dann die Ernüchterung: treequel benutzt ruby-ldap, was leider C Extensions benutzt, um die nativen Ldap Bibliotheken einzubinden. C Extensions sind aber weder portabel, noch intuitiv und: nicht unter jruby verwendbar – was wir als Standard Ruby VM benutzen.
Mit FFI gibt es aber eine schöne Möglichkeit portabel native Schnittstellen anzusprechen. Das kompilieren der nativen Erweiterungen fällt weg und man kann direkt die Bibliotheken benutzen. Da bietet es sich doch an das gleich mal mit der Ldap C Api auszuprobieren!
Zum Start ein kleines Beispiel, wie die Ldap Api mit FFI an ein Ruby Modul gebunden werden kann:
require 'ffi' module Ldap extend FFI::Library ffi_lib "/usr/lib/libldap.so" # LDAP ldap_open(char* host, int port); attach_function :ldap_open, [:string, :int], :pointer # int ldap_result(LDAP *ld,int msgid,int all,struct timeval *timeout,LDAPMessage **res); attach_function :ldap_result,[:pointer, :int, :int, :pointer, :pointer] , :int end
Mit attach_function werden die C Funktionen aus der Bibliothek libldap.so and dem Modul gebunden. Namen der Funktionen, Argumenttypen und Rückgabewert werden recht intuitiv mit Symbols definiert. Es existieren eine ganze Reihe Typen die verwendet werden können.
So kann dann eine mit FFI definierte Methode aufgerufen werden:
include Ldap con_handle = ldap_open 'x500.bund.de', 389
FFI bringt bei der Benutzung ein paar Erleichterungen mit: so wird in diesem Beispiel der Hostname als String übergeben und von FFI als char Pointer an die C Funktionen weitergegeben.
Die beliebten Pointer
Mit FFI können einfach Pointer erstellt und diese an die Funktionen weitergegeben werden. Dafür gibt es den MemoryPointer, der wie folgt benutzt wird:
# Pointer auf einen String str_ptr = FFI::MemoryPointer::from_string 'hello_joe' # und diesen string wieder lesen str_ptr.read_string # Pointer auf einen Int int_ptr = FFI::MemoryPointer::new :int int_ptr.write_int 4711
icht mehr durch Pointer referenzierte Speicherbereiche werden durch den GC aufgeräumt und FFI bietet mit ManagedStructs auch die Möglichkeit, am Aufräumen teilzunehmen.
Die Ldap C Api benutzt sehr häufig Pointer als Output Parameter, was eine gängige Vorgehensweise in der C Welt ist. Als Beispiel sei hier die Funktion ldap_result erwähnt, mit der Ergebnisse von asynchronen Operationen abgerufen werden können:
include Ldap # Pointer auf Pointer ldap_msgs = FFI::MemoryPointer.new :pointer ldap_result con_handle, msgs_id, nil, timeout, ldap_msgs # lese Pointer, in ldap_msgs stehen die Ergebnisse ldap_msgs = ldap_msgs.read_pointer
Null Pointer werden einfach als nil übergeben. Hier noch ein komplexeres Beispiel, um das Ergebnis einer Suchoperation in Ldap zu parsen:
# msgs_ptr ist ein Pointer auf das LdapMessage Struct def parse_search_result msgs_ptr # erstes Suchergebnis holen current_msg = ldap_first_entry con_handle, msg_ptr # Fortlaufender Index idx = FFI::MemoryPointer.new :pointer # Abfrage auf null Pointer until current_msg.null? # ersten Attributname des aktuellen elementes holen attrs = ldap_first_attribute con_handle, current_msg, idx # über alle attribute iterieren until attrs.null? # wert des attributes holen value_attr = ldap_get_values con_handle, current_msg, attrs p "#{attr} = #{value_attr.read_pointer.read_string}" # Value wieder freigeben ldap_value_free value_attr # nächsten attributname holen, dazu idx benutzen attrs = ldap_next_attribute con_handle, current_msg, idx.read_pointer end # nächstes suchergebnis holen current_msg = ldap_next_entry con_handle, current_msg end end
Das Pendant aus der C Welt sieht so aus:
// Taken from: http://www.ietf.org/rfc/rfc1823.txt /* step through each entry returned */ for ( e = ldap_first_entry( ld, res ); e != NULL; e = ldap_next_entry( ld, e ) ) { /* print each attribute */ for ( a = ldap_first_attribute( ld, e, &ptr ); a != NULL; a = ldap_next_attribute( ld, e, ptr ) ) { printf( "attribute: %s0", a ); /* print each value */ vals = ldap_get_values( ld, e, a ); for ( i = 0; vals[i] != NULL; i++ ) { printf( "value: %s0", vals[i] ); } ldap_value_free( vals ); } } /* free the search results */ ldap_msgfree( res );
Leider kann auch FFI die sperrige Schnittstelle nicht verbergen … Man erspart sich aber immerhin diesen Teil in C schreiben zu müssen.
Structs
Auch structs sind mit FFI ganz einfach:
# Definition des Structs class BerVal < FFI::Struct layout :bv_len, :uint, :bv_val, :pointer end # Zugriff auf die Werte im Hash Syntax ber_val = BerVal::new ber_val[:bv_len] = 3 ber_val[:bv_val] = 'foo'
Weitere Links
Hier gibt es eine kleine Übersicht, welche Projekte es mit FFI gibt. Die Wikiseite enthält schon viele Beispiele, ist an einigen Stellen, insbesondere bei den Pointern, aber noch etwas dürftig. Da hilft dann ein Blick ins rdoc.
Mit FFI gibt es eine gute Möglichkeit native Bibliotheken einzubinden, ohne auf die C Extensions zurückgreifen zu müssen und damit in der Ruby Welt zu bleiben.
Bei der Frage ob man nun ein Ldap Ruby Binding mit FFI baut oder direkt das Protokoll implementiert und auf Socketkommunikation setzt, sollte man bedenken dass mit FFI immer noch die C Entwicklungsbibliotheken auf Clientseite benötigt werden, was durchaus einen Nachteil darstellen kann. Auch die Unterstützung von unterschiedlichen Ldap Anbietern (Mozilla Ldap, Apache Directory) muss bei der Implementierung berücksichtigt werden.
Letzlich kann NiceFFI die Entwicklung mit FFI noch weiter vereinfachen.
Mehr? Aktuelle Artikel oder alle Artikel im Archiv.