ldapx(3tcl)

πŸ“¦
version 2.0 Β· 3 versions
, in debian trixie, ubuntu questing, ubuntu resolute, ubuntu stonking

πŸ’– ldapx β€” the LDAP extended object interface, bestie edition πŸ’–

okay so this is the page for ldapx, from the 3tcl manual. sit down, grab a strawberry drink πŸ“, we're doing LDAP and LDIF but make it cute.


✨ NAME ✨

ldapx β€” LDAP extended object interface. that's it, that's the name, short and iconic πŸ‘‘


✨ SYNOPSIS ✨

package require Tcl 8.5 9

package require ldapx ?1.3?

e reset

e dn ?newdn?

e rdn

e superior

e print

se isempty

se get attr

se get1 attr

se set attr values

se set1 attr value

se add attr values

se add1 attr value

se del attr ?values?

se del1 attr value

se getattr

se getall

se setall avpairs

se backup ?other?

se swap

se restore ?other?

se apply centry

ce change ?new?

ce diff new ?old?

la error ?newmsg?

la connect url ?binddn? ?bindpw? ?starttls?

la disconnect

la traverse base filter attrs entry body

la search base filter attrs

la read base filter entry ... entry

la commit entry ... entry

li channel chan

li error ?newmsg?

li read entry

li write entry

like, look at that. it's giving very structured. every one of those lines is a whole vibe of its own and we will get to each of them, no skipping, i promise 🫢


✨ DESCRIPTION ✨

so the ldapx package hands you an extended Tcl interface to LDAP directories and LDIF files. it's not doing the low-level stuff itself, nope β€” ldapx is built on top of the ldap package, which is the one that actually goes and gets the low level LDAP access. so ldapx is the glamorous older sibling and ldap is backstage doing the heavy lifting πŸ’…

now the compatibility tea, because this matters:


✨ OVERVIEW ✨

okay bestie this is the part where it all clicks, stay with me πŸ’œ

the ldapx package gives you objects for talking to LDAP directories and LDIF files, and it does it with an interface that's genuinely easy to use. under the hood it implements three snit::type classes. three. a whole little squad.

🌸 the entry class β€” the standard entry and the change entry

first up: the entry class. its whole job is storing individual entries.

and there are two different formats, which is the thing you have to internalise or nothing else makes sense:

  1. the standard format β€” this represents an entry the way it's read straight out of the directory. the original, the source of truth.
  2. the change format β€” this one stores the differences between two standard entries. it's not an entry, it's a receipt of what changed.

so with these entries, if your app wants to modify something in a directory, here's the whole dance:

  • read a (standard) entry from the directory
  • create a fresh copy into a new (standard) entry
  • modify that new copy
  • compute the differences between the two entries into a new (change) entry
  • and then commit that change entry to the directory

periodt. that's the pipeline ✨

now β€” this kind of modification happens so much that there's a glow-up for it: standard entries may contain their own copy of the original data. so with a backup copy in hand, the flow gets shorter and honestly way better:

  • read a (standard) entry from the directory
  • back up the original data
  • modify the entry
  • compute the differences between the entry and its backup

and those differences are what get committed to the directory. chef's kiss, no cap πŸ‘‘

there are also methods for all the supporting cast moves:

  • compute differences between two entries
  • apply differences to an entry so you get a new entry
  • get or set attributes in standard entries

🌈 the ldap class β€” connecting, traversing, committing

second class: ldap.

it gives you a method to connect and bind to the directory, with a uniform way of reaching both LDAP and LDAPS through a URL β€” that's ldap:// or ldaps://. one interface, two protocols, we love consistency πŸ¦‹

then there's the traverse control structure, which executes a body for each entry it finds in the directory. yes, a control structure. it's giving main character energy fr fr.

and the commit method applies some changes β€” represented as entry objects β€” to the directory.

one more thing and this one's important: since some attributes are represented as UTF-8 strings, the option -utf8 controls which attributes must be converted and which attributes must not be converted. remember that option, it comes back πŸ’«

πŸŽ€ the ldif class β€” channels and files

last but definitely not least: ldif.

it gives you a method to associate a standard Tcl channel to an LDIF object. once that's wired up, the methods read and write read or write entries from or to that channel. so simple it's almost rude.

this class can work with standard entries or change entries, depending on what type of LDIF file you're pointing it at β€” and the file may contain either standard entries or change entries, but not both at the same time. that's a hard no, bestie. pick a lane 🚫

and the option -utf8? works exactly the same as it does with the ldap class. consistency, we stan 🌟


okay that's the vibe and the vocabulary, and there's so much more coming β€” don't go anywhere, we're not done πŸ’–

πŸŽ€ ENTRY CLASS πŸŽ€

okay bestie, this is the entry class, the whole reason we're here πŸ’…. let's get into the data, no cap.

✨ ENTRY INSTANCE DATA ✨

An instance of the entry class keeps the following data, fr fr:

  • dn β€” This is the DN of the entry, which includes (in LDAP terminology) the RDN (relative DN) and the Superior parts. it's literally the whole address, bestie 🏠.
  • format β€” The format may be uninitialized (entry not yet used), standard or change. Most methods check the format of the entry, which can be reset with the reset method. so it's like the vibe check of the entry ✨.
  • attrvals β€” In a standard entry, this is where the attributes and associated values are stored. Many methods provide access to these informations. Attribute names are always converted into lower case. so like, UID becomes uid, periodt πŸ’….
  • backup β€” In a standard entry, the backup may contain a copy of the dn and all attributes and values. Methods backup and restore manipulate these data, and method diff may use this backup. it's the entry's little save file πŸŽ€.
  • change β€” In a change entry, these data represent the modifications. Such modifications are handled by specialized methods such as apply or commit. Detailed format should not be used directly by programs. so don't touch it directly, bestie, it's giving internal API πŸ™ˆ.

Internally, modifications are represented as a list of elements, each element has one of the following formats (which match the corresponding LDAP operations):

  1. {add {attr1 {val1...valn} attr2 {...} ...}} β€” Addition of a new entry.
  2. {mod {modop {attr1 ?val1...valn?} attr2 ...} {modop ...} ...} β€” Modification of one or more attributes and/or values, where <modop> can be modadd, moddel or modrepl (see the LDAP modify operation).
  3. {del} β€” Deletion of an old entry.
  4. {modrdn newrdn deleteoldrdn ?newsuperior?} β€” Renaming of an entry.

🌸 ENTRY OPTIONS 🌸

No option is defined by this class. literally zero, bestie. the vibes are minimal here 😌.

πŸ‘‘ METHODS FOR ALL KINDS OF ENTRIES πŸ‘‘

These methods work on every entry, no matter the format. slay.

  • e reset β€” This method resets the entry to an uninitialized state. it's like a factory reset, but make it fashion πŸ’….
  • e dn ?newdn? β€” This method returns the current DN of the entry. If the optional newdn is specified, it replaces the current DN of the entry. so you can read it or set it, iconic.
  • e rdn β€” This method returns the RDN part of the DN of the entry. that's the relative DN, the first part before the comma, iykyk.
  • e superior β€” This method returns the superior part of the DN of the entry. the rest of the address, bestie.
  • e print β€” This method returns the entry as a string ready to be printed. perfect for puts stdout, fr fr.

πŸ¦‹ METHODS FOR STANDARD ENTRIES ONLY πŸ¦‹

In all methods, attribute names are converted in lower case. so don't worry about casing, the entry handles it πŸ’–.

  • se isempty β€” This method returns 1 if the entry is empty (i.e. without any attribute).
  • se get attr β€” This method returns all values of the attribute attr, or the empty list if the attribute is not fond. (the manpage says "fond", we love a typo, but you know it means "found" 😭.)
  • se get1 attr β€” This method returns the first value of the attribute. just the first one, bestie.
  • se set attr values β€” This method sets the values (list values) of the attribute attr. If the list is empty, this method deletes all. (and that's literally where the sentence ends in the original, iconic πŸ’€.)
  • se set1 attr value β€” This method sets the values of the attribute attr to be an unique value value. Previous values, if any, are replaced by the new value. so it's a one-value situation.
  • se add attr values β€” This method adds all elements the list values to the values of the attribute attr. adding, not replacing, bestie.
  • se add1 attr value β€” This method adds a single value given by the parameter value to the attribute attr. one at a time, slay.
  • se del attr ?values? β€” If the optional list values is specified, this method deletes all specified values from the attribute attr. If the argument values is not specified, this method deletes all values. so you can be specific or go scorched earth πŸ”₯.
  • se del1 attr value β€” This method deletes a unique value from the attribute attr. one value, gone.
  • se getattr β€” This method returns all attributes names. all of them, periodt.
  • se getall β€” This method returns all attributes and values from the entry, packed in a list of pairs <attribute, list of values>. it's giving full data dump πŸ’….
  • se setall avpairs β€” This method sets at once all attributes and values. The format of the avpairs argument is the same as the one returned by method getall. so you can round-trip that data.
  • se backup ?other? β€” This method stores in an other standard entry object a copy of the current DN and attributes/values. If the optional other argument is not specified, copy is done in the current entry (in a specific place, see section OVERVIEW). it's like a save point πŸŽ€.
  • se swap β€” This method swaps the current and backup contexts of the entry. chaotic, but make it useful.
  • se restore ?other? β€” If the optional argument other is given, which must then be a standard entry, this method restores the current entry into the other entry. If the argument other argument is not specified, this methods restores the current entry from its internal backup (see section OVERVIEW). so you can restore from a friend or from your own backup.
  • se apply centry β€” This method applies changes defined in the centry argument, which must be a change entry. it's the moment of truth, bestie.

πŸ’œ METHODS FOR CHANGE ENTRIES ONLY πŸ’œ

  • ce change ?new? β€” If the optional argument new is specified, this method modifies the change list (see subsection Entry Instance Data for the exact format). In both cases, current change list is returned. Warning: values returned by this method should only be used by specialized methods such as apply or commit. so don't go poking around in there, bestie, it's not for direct use πŸ™ˆ.
  • ce diff new ?old? β€” This method computes the differences between the new and old entries under the form of a change list, and stores this list into the current change entry. If the optional argument old is not specified, difference is computed from the entry and its internal backup (see section OVERVIEW). Return value is the computed change list. it's like a diff but for LDAP, iconic.

ENTRY EXAMPLE

okay bestie, here's the full example, copied exactly, no cap. don't skip it, it's giving tutorial energy.

package require ldapx

#
# Create an entry and fill it as a standard entry with
# attributes and values
#
::ldapx::entry create e
e dn "uid=joe,ou=people,o=mycomp"
e set1 "uid"             "joe"
e set  "objectClass"     {person anotherObjectClass}
e set1 "givenName"       "Joe"
e set1 "sn"              "User"
e set  "telephoneNumber" {+31415926535 +2182818}
e set1 "anotherAttr"     "This is a beautiful day, isn't it?"

puts stdout "e\n[e print]"

#
# Create a second entry as a backup of the first, and
# make some changes on it.
# Entry is named automatically by snit.
#

set b [::ldapx::entry create %AUTO%]
e backup $b

puts stdout "$b\n[$b print]"

$b del  "anotherAttr"
$b del1 "objectClass" "anotherObjectClass"

#
# Create a change entry, a compute differences between first
# and second entry.
#

::ldapx::entry create c
c diff e $b

puts stdout "$c\n[$c print]"

#
# Apply changes to first entry. It should be the same as the
# second entry, now.
#

e apply c

::ldapx::entry create nc
nc diff e $b

puts stdout "nc\n[nc print]"

#
# Clean-up
#

e destroy
$b destroy
c destroy
nc destroy

πŸ‘‘ LDAP CLASS πŸ‘‘

πŸ’Ύ LDAP INSTANCE DATA πŸ’Ύ

When you make an instance of the ldap class, it's carrying around this data like it's her emotional support backpack:

  • channel β€” this is the channel the ldap package uses to talk to the LDAP server. it's the group chat with the server, basically.
  • lastError β€” this variable holds the error message that showed up in the most recent method of the ldap class (and bestie, this string gets modified in nearly all the methods, she's always updating). use the error method to go grab that message. 🫢

πŸŽ€ LDAP OPTIONS πŸŽ€

You set options on ldap instances using the configure method. slay.

The first option is the one for TLS parameters:

  • -tlsoptions list β€” specify the set of TLS options to use when connecting to the LDAP server (peep the connect method). for the list of valid options, go look at the LDAP package documentation, okay? ✨ The default is -request 1 -require 1 -ssl2 no -ssl3 no -tls1 yes -tls1.1 yes -tls1.2 yes. Example:
$l configure -tlsoptions {-request yes -require yes}

Then there's a whole set of options of the ldap class that get used during search operations (methods traverse, search and read, more on those below). πŸ‘‡

  • -scope base|one|sub β€” specify the scope of the LDAP search. it's one of base, one or sub, which mean a base object search, a one-level search, or a subtree search. the default is sub. πŸ¦‹
  • -derefaliases never|seach|find|always β€” specify how aliases dereferencing is handled: never means aliases are never dereferenced, always means aliases are always dereferenced, search means aliases get dereferenced when searching, and find means aliases are dereferenced only when locating the base object for the search. The default is never.
ok so heads up β€” the option value in the original is written as seach, and that is genuinely how it's written there, so keep that spelling in mind, iykyk πŸ™ˆ
  • -sizelimit integer β€” specify the max number of entries to be retrieved during a search. a value of 0 means no limit, she's limitless fr. Default is 0.
  • -timelimit integer β€” specify the time limit for a search to complete. a value of 0 means no limit. Default is 0.
  • -attrsonly 0|1 β€” specify if only attribute names are to be retrieved (value 1). normally (value 0), attribute values are also retrieved. Default is 0.

The last option is the one used when getting entries or committing changes in the directory:

  • -utf8 pattern-yes pattern-no β€” specify which attribute values are encoded in UTF-8. this info is specific to the LDAP schema your app is using, because some attributes β€” like jpegPhoto, for example β€” are not encoded in UTF-8. πŸ’… this option takes the form of a list with two regular expressions suitable for the regexp command (anchored by ^ and $). the first one specifies which attribute names are to be UTF-8 encoded, and the second one selects, among those, the attribute names that will not be UTF-8 encoded. so you can literally say: convert all attributes, except jpegPhoto. iconic. Default is {{.*} {}}, which means: all attributes are converted, no exceptions. periodt.

🌈 LDAP METHODS 🌈

  • la error ?newmsg? β€” this method returns the error message that happened in the last call to an ldap class method. if you pass the optional argument newmsg, it becomes the last error message. πŸ‘‘
  • la connect url ?binddn? ?bindpw? ?starttls? β€” this method connects to the LDAP server using the given url (which can be of the form ldap://host:port or ldaps://host:port). if the optional binddn argument is given together with the bindpw argument, the connect binds to the LDAP server using the specified DN and password. πŸ”

If the starttls argument is given a true value (1, yes, etc.) and the URL uses the ldap:// scheme, a TLS negotiation is initiated with the newly created connection, before LDAP binding. Default value: no.

This method returns 1 if the connection was successful, or 0 if an error occurred (use the error method to get the message).

  • la disconnect β€” this method disconnects (and unbinds, if necessary) from the LDAP server. bye bestie πŸ‘‹
  • la traverse base filter attrs entry body β€” this method is a new control structure. it searches the LDAP directory from the specified base DN (given by the base argument) and selects entries based on the filter argument. for each entry found, this method fetches the attributes specified by the attrs argument (or all attributes if it's an empty list), stores them in the entry instance of class entry and executes the script defined by the body argument. options are used to refine the search. ✨
⚠️ Caution: when this method is used, the script body cannot perform another LDAP search (methods traverse, search or read). do not do it. the vibes will not recover.
  • la search base filter attrs β€” this method searches the directory the same way as method traverse. all found entries are stored in newly created instances of class entry, which are returned in a list. the newly created instances should be destroyed when they're no longer used. be a responsible main character and clean up after yourself. 🧚
  • la read base filter entry ... entry β€” this method reads one or more entries, using the same search criteria as methods traverse and search. all attributes are stored in the entries. this method is a quick lil shortcut to read some entries. it returns the number of entries found in the directory (which may be more than the number of read entries). if you call it without any entry argument, it just returns the number of entries found, without returning any data. efficient queen. πŸ’…
  • la commit entry ... entry β€” this method commits the changes stored in the entry arguments. each entry may be either a change entry, or a standard entry with a backup. βœ…
πŸ“Œ Note: in the future, this method should use the LDAP transaction extension provided by OpenLDAP 2.3 and later.

🌸 LDAP EXAMPLE 🌸

okay bestie here's the whole thing in action, look at her go:

package require ldapx

#
# Connects to the LDAP directory using StartTLS
#

::ldapx::ldap create l
l configure -tlsoptions {-cadir /etc/ssl/certs -request yes -require yes}
set url "ldap://server.mycomp.com"
if {! [l connect $url "cn=admin,o=mycomp" "mypasswd" yes]} then {
      puts stderr "error: [l error]"
      exit 1
}

#
# Search all entries matching some criterion
#

l configure -scope one
::ldapx::entry create e
set n 0
l traverse "ou=people,o=mycomp" "(sn=Joe*)" {sn givenName} e {
      puts "dn: [e dn]"
      puts "  sn:        [e get1 sn]"
      puts "  givenName: [e get1 givenName]"
      incr n
}
puts "$n entries found"
e destroy

#
# Add a telephone number to some entries
# Note this modification cannot be done in the "traverse" operation.
#

set lent [l search "ou=people,o=mycomp" "(sn=Joe*)" {}]
::ldapx::entry create c
foreach e $lent {
      $e backup
      $e add1 "telephoneNumber" "+31415926535"
      c diff $e
      if {! [l commit c]} then {
          puts stderr "error: [l error]"
          exit 1
      }
      $e destroy
}
c destroy

l disconnect
l destroy

✨ LDIF CLASS ✨

Okay bestie, this is the one that talks to actual LDIF files β€” reading them, writing them, the whole glamorous situation. Let's get into it πŸ¦‹


πŸ’Ύ LDIF INSTANCE DATA πŸ’Ύ

So when you make yourself an instance of the ldif class, here's the little pile of data it's carrying around with it:

  • channel β€” the Tcl channel it uses to pull in or push out LDIF file contents. You hook an instance up to a channel with the channel method, and lowkey you do not need to tear that association down once the LDIF file operation is done. It can just chill there.
  • format β€” LDIF files can hold standard entries or change entries, but never both at once, okay? This variable holds the detected format of the file when you're reading, or the format of the entries you're writing when you're writing. It knows what it's doing.
  • lastError β€” the error message from the most recent ldif class method. Ngl this string gets modified in nearly every method, it's constantly catching strays 😭. Grab it with the error method.
  • version β€” the version of the LDIF file. And bestie, only version 1 is supported: read can only read version 1 files, and write only ever creates version 1 files. Periodt. πŸ’…

πŸŽ€ LDIF OPTIONS πŸŽ€

This class defines two options, and they're both kinda iconic:

  • -ignore list-of-attributes β€” use this to ignore certain attribute names while reading. For example, to read OpenLDAP replica files (replog), you must ignore replica and time attributes, because they simply do not conform to the RFC 2849 standard for LDIF files. Rude of them, honestly.

Default is an empty list: no attribute gets ignored.

  • -utf8 pattern-yes pattern-no β€” tells the class which attribute values are encoded in UTF-8. This is specific to the LDAP schema your application is using, because some attributes β€” jpegPhoto, for instance β€” are not UTF-8 encoded. The option takes a list of two regular expressions suitable for the regexp command (anchored by ^ and $). The first one picks which attribute names are UTF-8 encoded, and the second one selects, out of those, the attribute names that will not be UTF-8 encoded. So you can literally say: convert all the attributes, except jpegPhoto. Slay.

Default is {{.*} {}}, meaning: all attributes get converted, no exceptions. 🌈


πŸ› οΈ LDIF METHODS πŸ› οΈ

  • li channel chan β€” associates the Tcl channel named chan with this LDIF instance. Heads up: it resets the type of the LDIF object back to uninitialized. Fresh start energy.
  • li error ?newmsg? β€” returns the error message that happened in the last call to a ldif class method. If you hand it the optional newmsg argument, that becomes the last error message instead.
  • li read entry β€” reads the next entry from the LDIF file and stashes it in the entry object, which is of class entry. That entry can be a standard entry or a change entry, it's flexible like that ✨.
  • li write entry β€” writes the entry you pass in as entry out to the LDIF file.

🌸 LDIF EXAMPLE 🌸

This one is a whole vibe, watch:

package require ldapx

# This examples reads a LDIF file containing entries,
# compare them to a LDAP directory, and writes on standard
# output an LDIF file containing changes to apply to the
# LDAP directory to match exactly the LDIF file.

::ldapx::ldif create liin
liin channel stdin

::ldapx::ldif create liout
liout channel stdout

::ldapx::ldap create la
if {! [la connect "ldap://server.mycomp.com"]} then {
    puts stderr "error: [la error]"
    exit 1
}
la configure -scope one

# Reads LDIF file

::ldapx::entry create e1
::ldapx::entry create e2
::ldapx::entry create c

while {[liin read e1] != 0} {
    set base [e1 superior]
    set id [e1 rdn]
    if {[la read $base "($id)" e2] == 0} then {
        e2 reset
    }

    c diff e1 e2
    if {[llength [c change]] != 0} then {
        liout write c
    }
}

la disconnect
la destroy
e1 destroy
e2 destroy
c destroy
liout destroy
liin destroy

Like, the way that this reads from stdin, diffs against the directory, and writes changes to stdout?? Main character behavior, no cap. πŸ”₯


🌈 REFERENCES 🌈

(Yeah bestie, the original page just has the heading here and nothing under it. We love a mysterious queen who says nothing. πŸ™ˆ)


πŸ’€ BUGS, IDEAS, FEEDBACK πŸ’€

Okay so, real talk: this document β€” and the package it describes β€” will undoubtedly contain bugs and other problems. It's software, it happens. Please report them in the category ldap of the Tcllib Trackers at http://core.tcl.tk/tcllib/reportlist. And please also send any ideas for enhancements you have for either the package and/or the documentation, we're genuinely listening 🫢

When you're proposing code changes, please provide unified diffs β€” i.e. the output of diff -u. That's the good stuff.

Also note: attachments are strongly preferred over inlined patches. You can attach things by going to the Edit form of the ticket immediately after you create it, and then using the left-most button in the secondary navigation bar. iykyk. πŸ’…


🏷️ KEYWORDS 🏷️

directory access, internet, ldap, ldap client, ldif, protocol, rfc 2251, rfc 2849


🧚 CATEGORY 🧚

Networking


Copyright (c) 2006-2018 Pierre David <pdav@users.sourceforge.net>


And that's a wrap on the whole page, bestie! πŸŽ€ This came from the tcllib crew, version 1.3, and it's the ldapx(3tcl) manual page β€” so go forth, read your LDIF files, and be iconic. Byeeee πŸ’–βœ¨πŸ¦‹

πŸ“¦ where this page comes from