LDAP

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

See Also

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

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

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

LdapTcl

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

Clients

Tcllib
the ldap package provides an LDAP client. Based on Jochen Loewer's implementation.
Sensus ldap ,by Matt Newman
tclLDAP
tclLDAP-2.1.tar.gz , by Tom Murray
tclLdap-pkg
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

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

CGI Interface to LDAP

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 *

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

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 https://www.tcl-lang.org/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 {
            sAMAccountName
            name
            displayName
            employeeID
            pwdLastSet
            userAccountControl
            memberOf
            msExchHomeServerName
            msExchHideFromAddressLists
        }
        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

        return
    }
}; # end namespace

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

usage:

$ 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*