Version 40 of LDAP

Updated 2011-04-26 15:01:58 by rojo

LDAP Lightweight Directory Access Protocol is a protocol for networked directories [L1 ].

This page is about both TCL ldap libraries and TCL ldap clients.

As of 2010, the mostly referred to TCL ldap library is the one provided by tcllib: http://tcllib.sourceforge.net/doc/ldap.html

As of 2010, there is no up-to-date tcl GUI client. Legacy TCL project has a collection of TCL programs, among them there is a ldap client more than a decade old.


musashiXXX 2010-May-10 I've written a CGI based interface to LDAP. It was designed for the sole purpose of allowing users to administer a single OU from a web interface. It's very basic at the moment but I plan on developing it further. The tarball is located here: http://nefaria.com/scriptz/tcl-ldap-cgi.tar.gz ... there is no README or instructions (I'm working on that) but if you need any help, feel free to contact me (musashiXXX) on irc.freenode.net #tcl or via e-mail ([email protected]).


There are four independent (?) implementations, ldapTcl, Matt Newman's LDAP extension [L2 ], Jochen Loewer's (see below), a derivative of the latter available in tcllib as of 2004, and Gareth Owen's tclLdap-pkg [L3 ] (but is this a derivative of an early tclLdap?). It would be particularly useful to compare the latter two.


I did a pure Tcl implementation of a ldap interface in June 99. See (Jochen Loewer). It is running since then very nicely to query a large enterprise wide people directory. Right now only the read/query parts of the protocol are implemented. However, the internal ASN layer is there, so it should be rather easy to extend it. Unfortunately I didn't release the code to the public yet. I thought about that several times. It would be ideal for tcllib or even standard tcl (like the http package). CL: ooooo. ASN.1 would be nice for tcllib.

ak: Tcllib head provides an (incomplete) asn package. Directly derived from the code in the ldap package.


Interesting LDAP-based applications include Owen's Ldapper [L4 ] (which pages says it was last modified Wed Feb 3 17:46:54 GMT 1999).


As of 2004, the major category of LDAP servers are instances of Microsoft's Active Directory.


CL observes that learning to work with LDAP can intimidate newcomers, if only for the usual complication of client-server protocols (SNMP presents the same challenge): one must have a working server and client before achieving the "Hello, world" level of progress. In the '90s, there were quite a few public LDAP services, and it was inviting to connect new client applications to them for quick exercise. As far as I know, they're all gone now [task: confirm this]. Is there interest in Tclistan for a public LDAP server against which we can all practice? I might set one up ... (again, same's true for SNMP).


Patrick Finnegan provided several IBM-pertinent example LDAP-using scripts [L5 ] to the Cookbook.


In general, LDAP querying requires quoting of such characters as comma [L6 ].


schlenk The tcllib ldap client package was greatly enhanced in the 1.9 version of Tcllib.

  • STARTTLS support (RFC 4513)
  • SASL Auth support (RFC 4513)
  • new ldapx subpackage to provide an OO API
  • LDIF support in the ldapx package (RFC 2849)
  • asynchronous operation, does no longer block your app during ldap queries
  • Who am I extension supported (RFC 4532)
  • New introspection commands to inspect the running connection

Using LDIF you can for example parse Mozilla Thunderbird Addressbooks, which can be exported in ldif format, see the examples/ldap directory in the tcllib distribution.


LV 2009-Sep-15 Has anyone written a tutorial for learning to interact with LDAP (or even better, Active Directory) using Tcl? It would be nice to have a series of examples starting at something simple like looking for the current user's LDAP information, then on to listing all users along with their phone numbers, etc. and finally a few examples of things like adding a new user, updating an existing user, deleting a user. These examples would really help a novice administrator get started using Tcl.


rojo - 2011-04-26 10:20:20

@LV: Here's an example of an LDAP search in a MS environment for you.

#! /bin/sh
# \
exec tclsh "$0" ${1+"$@"}

namespace eval ldapsearch {

        set settings(domain) your.domain
        set settings(user) authorized_user
        set settings(pass) p4sSwh1rRed

        # timeout in seconds for LDAP queries... For some reason, when searching for cn,
        # results return immediately; whereas searching any other field (employeeID for
        # instance) will not return results until the timeout.  This doesn't seem to be
        # a TCL-only thing, as I've noticed it with ASP and .NET apps as well.  Maybe
        # my domain controllers have seen better days.  But I digress.  Set this low
        # for faster queries, but not low enough that every search returns 0 results.
        set settings(timeout) 5

        # Wildcard searches seem to error with ldap::secure_connect
        set settings(use-ssl) false

        variable settings

        package require ldap
        if {$settings(use-ssl)} { package require tls }

        proc isid {what} {
                # What does an employee ID look like in your organization?
                # For this example we'll say it's in the format of E01234567
                return [regexp -nocase {^E\d{8}$} $what]
        }

        proc search {what} {
                variable settings

                if {$settings(use-ssl)} {
                        if {[catch {ldap::secure_connect $settings(domain)} idx]} { set idx [ldap::connect $settings(domain)] }
                } {        set idx [ldap::connect $settings(domain)] }

                if {$settings(use-ssl) && [::ldap::info tls $idx]} {
                        puts "SSL connected to [::ldap::info ip $idx]"
                } { puts "Connected to [::ldap::info ip $idx]" }

                ldap::bind $idx $settings(user)@$settings(domain) $settings(pass)

                set attributes {
                        sAMAccountName
                        name
                        displayName
                        employeeID
                        pwdLastSet
                        userAccountControl
                        memberOf
                        msExchHomeServerName
                        msExchHideFromAddressLists
                }
                if {[isid $what]} {
                        set filter "|(employeeID=$what)"
                } elseif {[string match *,* $what]} {
                        set filter "|(displayName=$what*)"
                } else {
                        set filter "|(cn=$what)"
                }
                if {[catch {
                        set dc "dc=[string map {. ,dc=} $settings(domain)]"
                        ldap::searchInit $idx $dc $filter $attributes [list -scope sub -timelimit $settings(timeout)]
                } fail]} { puts "Init failure: $fail"; ldap::unbind $idx; ldap::disconnect $idx; return }

                while {![catch {ldap::searchNext $idx} flat]} {

                        set dn [lindex $flat 0]
                        set flat [lindex $flat 1]

                        if {![llength $flat]} { continue }
                        foreach attr $attributes { set res($attr) {} }
                        foreach {name val} $flat { set res($name) $val }


                        # $res(pwdLastSet) is measured in 100 nanosecond intervals since 1/1/1601
                        # convert to seconds
                        set res(pwdLastSet) [expr {wide($res(pwdLastSet) * pow(10,-7))}]
                        # convert to 1970 epoch
                        incr res(pwdLastSet) [clock scan {1601-1-1} -format {%Y-%m-%d}]
                        # expires when?
                        set res(pwdExpires) [clock format [clock add $res(pwdLastSet) 90 days] -format {%H:%M:%S %b %d %Y}]
                        # Set how long ago?
                        set res(pwdAge) "[expr {([clock seconds] - $res(pwdLastSet)) / 60 / 60 / 24}] days"


                        # see http://support.microsoft.com/kb/305144 for $res(userAccountControl)
                        set UAC {
                                16777216        TRUSTED_TO_AUTH_FOR_DELEGATION
                                8388608        PASSWORD_EXPIRED
                                4194304        DONT_REQ_PREAUTH
                                2097152        USE_DES_KEY_ONLY
                                1048576        NOT_DELEGATED
                                524288        TRUSTED_FOR_DELEGATION
                                262144        SMARTCARD_REQUIRED
                                131072        MNS_LOGON_ACCOUNT
                                65536        DONT_EXPIRE_PASSWORD
                                8192        SERVER_TRUST_ACCOUNT
                                4096        WORKSTATION_TRUST_ACCOUNT
                                2048        INTERDOMAIN_TRUST_ACCOUNT
                                512        NORMAL_ACCOUNT
                                256        TEMP_DUPLICATE_ACCOUNT
                                128        ENCRYPTED_TEXT_PWD_ALLOWED
                                64        PASSWD_CANT_CHANGE
                                32        PASSWD_NOTREQD
                                16        LOCKOUT
                                8        HOMEDIR_REQUIRED
                                2        ACCOUNTDISABLE
                                1        SCRIPT
                        }

                        set flags [list]
                        foreach {dec flag} $UAC {
                                if {!$res(userAccountControl)} { break }
                                if {$res(userAccountControl) >= $dec} { lappend flags $flag; incr res(userAccountControl) -$dec }
                        }
                        set res(userAccountControl) $flags

                        set groups [list]
                        foreach group $res(memberOf) {
                                lappend groups [string map {CN= ""} [lindex [split $group ,] 0]]
                        }
                        set res(memberOf) $groups

                        set res(msExchHomeServerName) [lindex [split [lindex $res(msExchHomeServerName) end] =] end]

                        foreach {name val} [array get res] { puts "$name: $val" }
                        puts "\n"
                }
                # puts "Last value of \$flat: $flat"
                ldap::searchEnd $idx
                ldap::unbind $idx
                ldap::disconnect $idx

                return
        }
}; # end namespace

if {[llength $argv]} { puts $argv; ldapsearch::search [lindex $argv 0] }

usage: $ tclsh % source thisfile.tcl % ldapsearch::search username - outputs records for all users matching username* similar to Active Directory Users & Computers % ldapsearch::search {lastname, f} - outputs records for all users matching "lastname, f*" similar to Active Directory Users & Computers % ldapsearch::search employeeID - outputs exact match where employeeID=searchterm