ldapx(3tcl)
π 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:
- LDAP access is compatible with RFC 2251 (
http://www.rfc-editor.org/rfc/rfc2251.txt) - LDIF access is compatible with RFC 2849 (
http://www.rfc-editor.org/rfc/rfc2849.txt)
β¨ 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:
- the standard format β this represents an entry the way it's read straight out of the directory. the original, the source of truth.
- 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 beuninitialized(entry not yet used),standardorchange. Most methods check the format of the entry, which can be reset with theresetmethod. 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,UIDbecomesuid, periodt π .backupβ In a standard entry, the backup may contain a copy of thednand all attributes and values. Methodsbackupandrestoremanipulate these data, and methoddiffmay 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 asapplyorcommit. 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):
{add {attr1 {val1...valn} attr2 {...} ...}}β Addition of a new entry.{mod {modop {attr1 ?val1...valn?} attr2 ...} {modop ...} ...}β Modification of one or more attributes and/or values, where<modop>can bemodadd,moddelormodrepl(see the LDAP modify operation).{del}β Deletion of an old entry.{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 optionalnewdnis 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 forputs 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 returns1if the entry is empty (i.e. without any attribute).se get attrβ This method returns all values of the attributeattr, 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 (listvalues) of the attributeattr. 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 attributeattrto be an unique valuevalue. 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 listvaluesto the values of the attributeattr. adding, not replacing, bestie.se add1 attr valueβ This method adds a single value given by the parametervalueto the attributeattr. one at a time, slay.se del attr ?values?β If the optional listvaluesis specified, this method deletes all specified values from the attributeattr. If the argumentvaluesis 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 attributeattr. 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 theavpairsargument is the same as the one returned by methodgetall. so you can round-trip that data.se backup ?other?β This method stores in anotherstandard entry object a copy of the current DN and attributes/values. If the optionalotherargument 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 argumentotheris given, which must then be a standard entry, this method restores the current entry into theotherentry. If the argumentotherargument 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 thecentryargument, which must be a change entry. it's the moment of truth, bestie.
π METHODS FOR CHANGE ENTRIES ONLY π
ce change ?new?β If the optional argumentnewis 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 asapplyorcommit. 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 thenewandoldentries under the form of a change list, and stores this list into the current change entry. If the optional argumentoldis 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 theldappackage 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 theldapclass (and bestie, this string gets modified in nearly all the methods, she's always updating). use theerrormethod 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 theconnectmethod). 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 ofbase,oneorsub, which mean a base object search, a one-level search, or a subtree search. the default issub. π¦-derefaliases never|seach|find|alwaysβ specify how aliases dereferencing is handled:nevermeans aliases are never dereferenced,alwaysmeans aliases are always dereferenced,searchmeans aliases get dereferenced when searching, andfindmeans aliases are dereferenced only when locating the base object for the search. The default isnever.
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 of0means no limit, she's limitless fr. Default is0.-timelimit integerβ specify the time limit for a search to complete. a value of0means no limit. Default is0.-attrsonly 0|1β specify if only attribute names are to be retrieved (value1). normally (value0), attribute values are also retrieved. Default is0.
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 β likejpegPhoto, for example β are not encoded in UTF-8. π this option takes the form of a list with two regular expressions suitable for theregexpcommand (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, exceptjpegPhoto. 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 anldapclass 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 formldap://host:portorldaps://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 classentryand 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 (methodstraverse,searchorread). do not do it. the vibes will not recover.
la search base filter attrsβ this method searches the directory the same way as methodtraverse. all found entries are stored in newly created instances of classentry, 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 methodstraverseandsearch. 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 thechannelmethod, 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 recentldifclass method. Ngl this string gets modified in nearly every method, it's constantly catching strays π. Grab it with theerrormethod.versionβ the version of the LDIF file. And bestie, only version 1 is supported:readcan only read version 1 files, andwriteonly ever creates version 1 files. Periodt. π
π LDIF OPTIONS π
This class defines two options, and they're both kinda iconic:
-ignorelist-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.
-utf8pattern-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 theregexpcommand (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, exceptjpegPhoto. 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 aldifclass 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 classentry. 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 π
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
- debian trixie: tcllib 2.0+dfsg-4 Β· Tcl
- ubuntu questing: tcllib 2.0+dfsg-4
- ubuntu resolute: tcllib 2.0+dfsg-5 Β· Tcl
- ubuntu stonking: tcllib 2.0+dfsg-6 Β· Tcl
- Copyright (c) 2006-2018 Pierre David <pdav@users\&.sourceforge\&.net>
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms