Updated 2015-03-30 12:35:10 by dbohdan

LDAP, an acronym for Lightweight Directory Access Protocol, is a protocol for

See Also  edit

Microsoft's Active Directory
a major category of LDAP servers
ActiveState Tcl Cookbook tag: ldap
[Patrick Finnegan] provided several IBM-pertinent example LDAP-using scripts to the Cookbook.
using ldap, comma in dn ,comp.lang.tcl ,2009-06-22
In general, LDAP querying requires quoting of such characters as comma

Description  edit

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).

Tutorial Suggestions  edit

Examples of the following are solicited:
looking for the current user's LDAP information
listing all users along with their phone numbers, etc
adding a new user
updating an existing user
deleting a user

Servers  edit


Jochen Loewer's (see below), a derivative of the latter available in tcllib as of 2004,

Clients  edit

the ldap package provides an LDAP client. Based on Jochen Loewer's implementation.
Sensus ldap ,by Matt Newman
tclLDAP-2.1.tar.gz , by Tom Murray
a repackaging by Gareth Owen of Tony Murray's tclLdap2.1 extension as a tcl8.x dynamic library. tclLdap-pkg-1.2.tar.gz (alternate)
Legacy TCL project
has a collection of TCL programs, among them there is a ldap client more than a decade old.

As of 2010, there is no up-to-date tcl GUI client.

Other Programs  edit

a tcl/tk LDAP search tool. ldapper-1.2.tar.gz(alternate)

CGI Interface to LDAP  edit

musashiXXX 2010-05-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]).

Tcllib lap package * edit

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.

Example: LDAP search in an MS Environment  edit

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

An example of an LDAP search in a MS environment:
#! /bin/sh
# \
exec tclsh "$0" ${1+"$@"}

namespace eval ldapsearch {

    set settings(domain) your.domain
    set settings(user) authorized_user
    set settings(pass) p4sSwh1rRed
    # for pw_expires, use "never" or keywords compatible with clock arithmetic
    # see http://www.tcl.tk/man/tcl/TclCmd/clock.htm#M22
    set settings(pw_expires) {90 days}
    set settings(server_timezone) GMT
    set settings(client_timezone) US/Eastern

    # timeout in seconds for LDAP queries... For some reason, when searching for cn
    # or displayName, results return instantly; whereas searching any other field
    # (employeeID for instance) returns results in *exactly* the number of seconds
    # in this timeout setting, regardless of the actual time needed to complete the
    # search.  Perhaps the Windows domain controller does not send its EOF-ish search
    # complete signal when searching non-indexed columns the same as it does for
    # sAMAccountName / cn / displayName / etc.  But I digress.  Set this low for
    # faster queries, but not low enough that valid searches return 0 results.
    set settings(timeout) 8

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

     # End of user variables #

    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
        global env

        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 {
        if {[isid $what]} {
            set filter "(employeeID=$what)"
            set options [list -scope sub -timelimit $settings(timeout) -sizelimit 1]
        } else {
            set filter "|(cn=$what*)(displayName=$what*)"
            set options [list -scope sub -timelimit $settings(timeout) -sizelimit 20]
        if {[catch {
            set dc "dc=[string map {. ,dc=} $settings(domain)]"
            ldap::searchInit $idx $dc $filter $attributes $options
        } 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 }

            # Interpret dates from the server's time zone, displaying them in the client's
            if {[info exists env(TZ)]} { set keepTZ $env(TZ) } { set keepTZ $settings(client_timezone) }
            set env(TZ) $settings(server_timezone)
            # $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?
            if {![regexp {\d} $settings(pw_expires)]} {
                set res(pwdExpires "never"
            } else {
                set dur [lindex $settings(pw_expires) 0]
                set unit [lindex $settings(pw_expires) 1]
                set res(pwdExpires) [clock format [clock add $res(pwdLastSet) $dur $unit]\
                -format {%+} -timezone $settings(client_timezone)]
            # Set how long ago?
            set res(pwdAge) "[expr {([clock seconds] - $res(pwdLastSet)) / 60 / 60 / 24}] days"
            # Math finished.  Make pwdLastSet human readable now.
            set res(pwdLastSet) [clock format $res(pwdLastSet) -format {%+} -timezone $settings(client_timezone)]
            # restore temporarily changed env(TZ)
            set env(TZ) $keepTZ

            # see http://support.microsoft.com/kb/305144 for $res(userAccountControl)
            set UAC {
                134217728        UF_USE_AES_KEYS
                67108864        UF_PARTIAL_SECRETS_ACCOUNT
                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

}; # end namespace

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

$ tclsh
% source thisfile.tcl
% ldapsearch::search employeeID
- outputs exact match where employeeID=searchterm
% ldapsearch::search username
- outputs records for (up to 20) accounts matching cn=searchterm* or displayName=searchterm*