The LDAP Heavyweight
LDAP Schema Management With Netscape Tools and Java
By Mark Wilcox
Send comments and questions about this article to View Source.
Click here for printer-friendly version
Every LDAP server uses a schema, which is just a fancy word for database
design. Every database that you use, whether it's a simple flat file stored
in a comma-delimited format or a sophisticated Oracle database with a thousand
tables joined together in complex relationships, has a schema.
You might be wondering what databases have to do with LDAP. Well, behind
every LDAP server there's a database. LDAP is just a standard protocol
for talking to a database over the Internet. The LDAP server stores its
information in a database of one flavor or another. Some systems use a
standard relational database, while others use a proprietary format (such
as Novell or Microsoft Exchange). Netscape Directory Server uses a highly
modified version of the Berkeley v2 database format. Netscape's
databases are optimized for LDAP operations and provide features like transaction
management, which allows you to recover in case something catastrophic
should happen to the system. If you'd rather use a different database format
than the one Netscape provides, you can do so with the Netscape Server
Plug-In API (NSAPI).
When we talk about a database, we're really referring to the fields
and tables in the database. In LDAP, fields and tables are known as attributes
and object classes, respectively. If you're new
to LDAP terminology and concepts, you might want to check out some of the
resources listed at the end of this article.
Back to schemas: Again, every LDAP server relies on a schema for its
database design. One difference between LDAP and the database formats you
might be familiar with (for example, a relational database like Oracle
or Access) is that every LDAP server has a common, standard schema that
you can access. This means that everyone who writes an LDAP client can
expect the LDAP server to have some standard object classes and attributes.
For example, if I'm going to search an LDAP server and I don't know anything
about it except that it allows public searching of entries that match the
inetOrgPerson object class, I can expect to find certain standard
attributes: I'll know that at the very least the database will contain
each person's full name (represented by the cn attribute) and
surname (the sn attribute). I can also expect to find common (but
optional) attributes such as a person's email address (the mail attribute)
and first name (the givenname attribute). These attributes apply
to all LDAP servers, whether Netscape's or those that comply with the Microsoft
Exchange LDAP interface, OpenLDAP, or Sun Directory Service.
A popular misconception of LDAP is that it's inflexible. This may stem
from the fact that the X.500 protocol from which LDAP is derived was fairly
rigid in its implementation. LDAP - in particular, version 3 of LDAP -
is as flexible as any database format. It has a fixed set of attributes
and object classes simply to ensure that it remains interoperable between
different LDAP-speaking clients.
EXTENDING OR MODIFYING A SCHEMA: WHY AND HOW
LDAP provides such a rich set of object classes and attributes that at
first I couldn't think of why I'd want to extend the standard schema. I
thought that surely the inetOrgPerson object class had all the
attributes I'd ever need for directory services at the University of North
Texas (UNT). It turns out that that was very naive and small-minded of
me. The designers of LDAP thought of many things, but they couldn't allow
for everything that could possibly be needed in a schema. As you build
your directory services, you'll surely discover additional attributes that
you'll need.
For example, we made such a discovery at UNT when we migrated a database
of student and staff information from a Sun NIS infrastructure to LDAP.
(For more on the LDAP-NIS gateway, see PADL
Software's web site.) We needed to record each person's middle name,
but the standard inetOrgPerson object class has no concept of
a middle name. In the existing database there was also an other field
that could contain a person's former last name (if it had changed after
marriage, for instance), a professional title (such as "Dr."), or a name
suffix (such as "Jr." or "III"). So we needed two new attributes, middlename
and
other. We also needed a way to keep track of student and staff ID
numbers, including former ID numbers if they'd changed. And we wanted to
keep all the relevant information about a person in a single entry.
If you look at any of the LDAP vendors, such as Netscape, Sun, and Microsoft,
you'll see that they've added object classes and attributes to
provide extra functionality. In fact, the entire Netscape
version 4 line of servers (which is expected to ship by the end of June)
will store all their configuration information in
LDAP directories. Sun uses LDAP for the storage of distributed Java objects,
and Microsoft has added all sorts of object classes and attributes for
the Active Directory service because they want you to manage your entire
network through it.
There are
three options for adding to or otherwise modifying a schema:
-
Using the Netscape Directory Server Admin screen, or Netscape Console if
you use Netscape Directory Server 4. This is fairly straightforward, so I'm just
going to point you to the server
documentation for details.
-
Programmatic access, using either command-line utilities or
the Netscape Directory SDK for Java. This isn't all that hard, especially if
you use Java. The rest of this article describes these approaches.
-
Modifying the user_at.conf and user_oc.conf files (but
not any other .conf file). This is recommended only if you're sure of
what you're doing, and only as a last resort.
USING THE COMMAND-LINE UTILITIES
Getting at the schema of an LDAP server is easy if the server supports
LDAP v3. In LDAP v3 there's a special entry, cn=schema, that contains
all the schema information for the server. It has two attributes:
-
objectclasses, which refers to the object class definitions
-
attributetypes, which refers to attribute definitions
To get the schema information for an LDAP server with the ldapsearch
command-line
utility, you specify a search base of cn=schema (with the command
switch -b "cn=schema") and provide any search filter, such as
"objectclass=*". You must also specify that your client is capable
of communicating via the LDAP v3 protocol. LDAP v3 is the default version
for the ldapsearch utility that ships with Netscape Directory
Server 3 and later (as well as the version that ships with the Netscape
Directory SDK for C), so you don't have to do anything special.
The server will display a long, ugly-looking
list of object class definitions that looks something like this:
dn: cn=schema
objectclasses: ( 2.5.6.0 NAME 'top' DESC 'Standard
ObjectClass' MUST ( objectclass ) MAY ( aci ) )
objectclasses: ( 2.5.6.1 NAME 'alias' DESC 'Standard
ObjectClass' SUP 'top' MUST ( objectclass $ aliasedobjectname
) MAY ( aci ) )
objectclasses: ( 2.5.6.7 NAME 'organizationalPerson'
DESC 'Standard ObjectClass' SUP 'person' MUST ( objectclass
$ sn $ cn ) MAY ( aci $ description $ seealso $ telephonenumber $
userpassword $ destinationindicator $ facsimiletelephonenumber $ internationalisdnnumber
$ l $ ou $ physicaldeliveryofficename $ postofficebox $ postaladdress $
postalcode $ preferreddeliverymethod $ registeredaddress $ st $ street
$ teletexterminalidentifier $ telexnumber $ title $ x121address )
)
Most people panic when they first see this display, but it's easy to
parse (say, with Perl). It breaks down like this:
-
The object identifier (OID) of the object class
-
The name of the object class (NAME)
-
The description of the object class (DESC)
-
The object class this class derives from, referred to as the superior object
class (SUP)
-
The required attributes (MUST)
followed by the allowed attributes (MAY). The individual attributes
are separated by $ symbols.
Note: OIDs are used for identifying names in LDAP to make it easier
on the computer, not the user. Computers are very good at doing number
comparisons, but horrible at name comparisons, because name matching relies
a lot on context. For example, "Mark" can refer to my name or it can be a descriptive
term, as in "He bears the mark of Mozilla." Also, OIDs are guaranteed to
be unique, unlike human names.
Similarly, the display of attribute definitions looks
like this:
attributetypes: ( abstract-oid NAME 'abstract'
DESC 'Standard Attribute' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
attributetypes: ( 2.16.840.1.113730.3.1.95 NAME
'accountUnlockTime' DESC 'Standard Attribute' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
)
This display shows the following for each attribute:
-
The OID of the attribute
-
The name of the attribute (NAME)
-
The description of the attribute (DESC)
-
The OID of the attribute's syntax (SYNTAX)
Notice that some OIDs aren't numbers but are instead names that represent
OID numbers. Netscape Directory Server lets you to do this as a convenience.
You may be bothered by the fact that the attribute's syntax is shown
as an OID rather than in an easily understandable form - for example, as
1.3.6.1.4.1.1466.115.121.1.15 rather than the description "case-ignore
string." To make it easier on you, Table 1 shows the relationship of the
syntax OIDs to human-readable attribute syntax.
Table 1. OIDs vs. human-readable syntax
| OID |
Format |
| 1.3.6.1.4.1.1466.115.121.1.15 |
cis (case-ignore string) |
| 1.3.6.1.4.1.1466.115.121.1.5 |
bin (binary) |
| 1.3.6.1.4.1.1466.115.121.1.50 |
tel (telephone number) |
| 1.3.6.1.4.1.1466.115.121.1.26 |
ces (case-exact string) |
| 1.3.6.1.4.1.1466.115.121.1.27 |
int (integer) |
| 1.3.6.1.4.1.1466.115.121.1.12 |
dn (distinguished name) |
In case you think I figured this out by reading the LDAP RFCs, you're
wrong: instead I downloaded the source
code to the Netscape Directory SDK for Java and looked in its schema
classes to figure out the relationship of the OIDs to human-readable syntax.
Adding an Attribute and an Object Class
To use the ldapmodify command-line utility to add a new attribute
called middleName with a syntax of case-ignore string (meaning
that a search or comparison will ignore case), we'd need LDIF code that
looks like this:
# OIDs for examples start at 1.2.3.4.5.17. For real use, get your own!
dn: cn=schema
changetype: modify
add: attributetypes
# Attribute is middleName, with a syntax of case-ignore string
attributetypes: (1.2.3.4.5.17.1 NAME 'middleName' DESC 'A middle name'
SYNTAX '1.3.6.1.4.1.1466.115.121.1.15')
If we save this code to a file named attribute.ldif, we can add
it to the server with
ldapmodify -h localhost -D "cn=Directory Manager" -w "password" -f attribute.ldif
at the command prompt (for example, the C:\ prompt in Windows
NT or the % in Unix).
Next we need to add an object class that has this attribute. Here's
the LDIF code:
dn: cn=schema
changetype: modify
# A new object class: testPerson
# This extends inetOrgPerson to allow for middleName.
add: objectclasses
objectclasses: (1.2.3.4.5.17.2 NAME 'testPerson' SUP 'inetOrgPerson'
MUST (objectclass $ sn $ cn) MAY (middleName))
If we save this code to a file named objectclass.ldif, we can
add the object class with the following at the command prompt:
ldapmodify -h localhost -D "cn=Directory Manager" -w "password" -f objectclass.ldif
Note that to modify the server schema you must log in as the Directory
Manager, since no other user has write permission on the schema.
Adding a User With the New Attribute
Now we can add a user that has a middleName attribute, with this
LDIF code:
dn: uid=testuser,ou=People,o=airius.com
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
objectclass: testPerson
uid: testuser
mail: testuser@airius.com
cn: John Test User
givenname: John
givenname: John Test
middlename: Test
sn: User
ou: People
ou: Accounting
If we save this code to a file named user.ldif, we can add the
object class with
ldapmodify -h localhost -D "cn=Directory Manager" -w "password" -f user.ldif
You'll notice that we added attributes for the user that aren't specified
in the testPerson object class. This is because LDAP is object-oriented
and testPerson is derived from inetOrgPerson. Thus
testPerson inherited all the attributes that were part of inetOrgPerson,
which inherited attributes from organizationalPerson, which inherited
attributes from person, which finally inherited attributes from
top, the root object class.
To add a user, you can log in as any user who has rights to add an entry
to the server, not just as the Directory Manager.
Getting Your Own OID
It's easy to get an OID: You can go to ANSI
(American National Standards Institute) and pay $1000 for a number, or
to IANA (Internet Assigned Numbers Authority)
and get one for free.
Actually, IANA issues "private enterprise numbers" rather than OIDs.
If you get one of these numbers
from them, you'll need to append it to 1.3.6.1.4.1, which is the official
private Internet OID. For example, if you fill out the IANA forms and
(typically after about a week) get the private enterprise number 5131,
your OID will be rooted at 1.3.6.1.4.1.5131. Your first new attribute or
object class could then be 1.3.6.1.4.1.5131.1. There's no true regulation
for OID numbers, just as long as they're unique. To help guarantee uniqueness,
you can register your OIDs at the Object
Identifiers Registry. This site is the semiofficial central
registry for OIDs for any protocol, though IANA also has a
registry
of all assigned numbers.
To quote Mark Smith (one of the creators of LDAP and Netscape's
Directory Server Architect), "The bottom line is that an OID
is an OID is an OID - as long as you have been assigned one from someone who can trace
the lineage back to the root of all OIDs (set up by the ITU), you can use
an OID for anything you like."
USING THE NETSCAPE DIRECTORY SDK AND JAVA
Now that we've seen how it's possible to deal with the server schema information
with the command-line utilities, you'll probably want an easier way.
In my opinion, the easiest way
to deal with schema information from a programming standpoint is to use the
Netscape Directory SDK for Java, which contains some special classes
for this purpose (see Table 2).
Table 2. Schema-related classes in the Directory SDK for Java
| Class name |
Description |
| LDAPSchema |
The schema used by an LDAP server |
| LDAPSchemaElement |
The base class that represents a generic element in the schema |
| LDAPObjectClassSchema |
An object class description in the schema |
| LDAPAttributeSchema |
An attribute description |
| LDAPMatchingRuleSchema |
How the
server will handle matches (for example, whether a match will equal the entire string or just part of
it) |
You use the LDAPSchema class to get a copy of the server's
schema via an LDAPConnection object, and then the rest of the
classes to get schema information or set properties of the schema. (Remember
that if you want to set properties of the schema, you must log in as the
Directory Manager.)
The example program schemaTest.java (Listing 1) obtains the
schema object from the server and prints out the inetOrgPerson object
class and the mail attribute. It then adds a middleName attribute,
along with a testPerson object class that extends inetOrgPerson
by
adding the middleName attribute, just as in our earlier example.
Most of this code has been lifted from the Java SDK documentation.
Listing 1. schemaTest.java, for dealing with LDAP
schema information
import netscape.ldap.*;
import java.util.*;
public class schemaTest {
public static void main(String[] args) {
LDAPConnection ld = new LDAPConnection();
String hostname = "localhost";
int portnumber = LDAPv2.DEFAULT_PORT;
String bindDN = "cn=Directory Manager";
String bindPW = "jessica98";
/* Construct a new LDAPSchema object to hold the schema that you want to
retrieve. */
LDAPSchema dirSchema = new LDAPSchema();
try {
ld.connect(hostname, portnumber, bindDN, bindPW);
/* Get the schema from the directory. Anonymous Case Exact Strings OK. */
dirSchema.fetchSchema(ld);
/* Get and print the definition of the inetOrgPerson object class. */
LDAPObjectClassSchema objClass = dirSchema.getObjectClass("inetOrgPerson");
if (objClass != null)
System.out.println("inetOrgPerson := " + objClass.toString());
/* Get and print the definition of the userPassword attribute. */
LDAPAttributeSchema attrType = dirSchema.getAttribute("mail");
if (attrType != null)
System.out.println("mail := " + attrType.toString());
System.out.println("------------------------------------");
System.out.println("Adding middleName attribute");
/* Create a new attribute for Java. */
LDAPAttributeSchema newAttrType = new LDAPAttributeSchema("middleName",
"1.2.3.4.5.17.1", "A middle name", LDAPAttributeSchema.cis, false);
/* Add the new attribute type to the schema. */
newAttrType.add(ld);
System.out.println("Now adding new test object class");
/* Add a new object class. */
/* Note you need Directory Server 4 or later for this part because of bug in
version 3 with adding an object class that is a subclass of an existing class. */
String[] requiredAttrs = new String[1];
String[] optionalAttrs = new String[1];
requiredAttrs[0] = "";
optionalAttrs[0] = "middleName";
LDAPObjectClassSchema newObjClass = new LDAPObjectClassSchema("testPerson",
"1.2.3.4.5.17.2", "inetOrgPerson", "A Test Person", requiredAttrs,
optionalAttrs);
/* Add the new object class to the schema. */
newObjClass.add(ld);
/* Fetch the schema again from the server to verify that the changes were made. */
dirSchema.fetchSchema(ld);
/* Get and print the new attribute type. */
newAttrType = dirSchema.getAttribute("middleName");
if (newAttrType != null)
System.out.println("middleName = " + newAttrType.toString());
/* Get and print the new object class. */
newObjClass = dirSchema.getObjectClass("testPerson");
if (newObjClass != null)
System.out.println("testPerson = " + newObjClass.toString());
ld.disconnect();
} catch (Exception e) {
System.err.println(e.toString());
System.exit(1);
}
System.exit(0);
}
}
The add.java program (Listing 2) adds a user of the testPerson
object
class to the server. In this program, we not only set an attribute
called middleName with a value of "Edward", but we also
add a value in both the cn and givenName attributes that
includes "Edward". This is because local clients will likely know
about the middleName attribute, but outside clients that might
be searching the directory (such as the clients built into Communicator)
will probably not know about it. So to allow outside clients to search
for someone by middleName, we also include this information in
the standard attributes cn and givenName. Local clients
will know to how to get the definitive middle name, while the general public
will appreciate being able to fully search the directory.
Note: Due to a bug in Netscape Directory Server 3, this example
won't work in that version. You must use version 4 or later.
Listing 2. add.java, for adding a new user of the
testPerson object class
import netscape.ldap.*;
import java.io.*;
import java.util.*;
public class add {
public static void main(String args[]) {
String host = "localhost";
int port = 389;
String base = "o=airius.com";
int scope = LDAPv2.SCOPE_SUB;
String dn = "uid=kvaughan, ou=People, o=airius.com";
String pwd = "bribery";
String new_dn = "uid=mewilcox,ou=People,o=airius.com";
String objectclass_values [] = {"top", "person", "organizationalperson",
"inetorgperson", "testPerson"};
String cn_values [] = {"Mark Wilcox", "Mark Edward Wilcox"};
String sn_values [] = {"Wilcox"};
String givenname_values [] = {"Mark", "Mark Edward"};
String ou_values [] = {"People", "Accounting"};
String uid_values [] = {"mewilcox"};
String mail_values[] = {"mewilcox@airius.com"};
String mn_values[] = {"Edward"};
LDAPAttributeSet attrib_set = null;
LDAPAttribute attribute = null;
LDAPEntry entry = null;
LDAPConnection ld = null;
try {
ld = new LDAPConnection();
/* Must bind as a user with rights to write to the server */
ld.connect(host,port,dn,pwd);
attrib_set = new LDAPAttributeSet();
attribute = new LDAPAttribute("objectclass", objectclass_values);
attrib_set.add(attribute);
attribute = new LDAPAttribute("cn", cn_values);
attrib_set.add(attribute);
attribute = new LDAPAttribute("sn", sn_values);
attrib_set.add(attribute);
attribute = new LDAPAttribute("givenname",givenname_values);
attrib_set.add(attribute);
attribute = new LDAPAttribute("mail", mail_values);
attrib_set.add(attribute);
attribute = new LDAPAttribute("ou", ou_values);
attrib_set.add(attribute);
attribute = new LDAPAttribute("middleName", mn_values);
attrib_set.add(attribute);
attribute = new LDAPAttribute("uid", uid_values);
attrib_set.add(attribute);
/* Create the entry object. */
entry = new LDAPEntry(new_dn, attrib_set);
/* Add the object. */
ld.add(entry);
if (ld != null)
ld.disconnect();
} catch(LDAPException e) {
e.printStackTrace();
}
}
}
CONCLUSION
LDAP server schema management is an important part of integrating LDAP
into your organization. It gives you the benefits of traditional database
design, such as the flexibility to build tables (object classes) with
fields (attributes) as you see fit, yet you'll still enjoy the benefit
of LDAP: the standard, core attributes of a standard set of object classes.
A final note: I've compiled a set of LDAP FAQs and posted them on all
the Netscape LDAP newsgroups, as well as on my LDAP
Root site; I'll be updating the FAQs on a regular basis.
Until next time.
FURTHER RESOURCES
View Source wants your feedback!
Write to
us and let us know
what you think of this article.
I'd like to thank Rob Weltman for always answering
my Java and Directory SDK questions. He's also the inspiration behind putting
the new FAQs together and getting them posted on the newsgroups.
Mark Wilcox
is the web administrator at the University
of North Texas. He's been involved with LDAP since the alpha-release
days of Netscape Directory Server 1.0. When not working with web technologies,
he likes to spend time with his wife Jessica.
- Related Readings:
-
Any sample code included above is provided for your use on an "AS IS" basis, under the Netscape License Agreement - Terms of Use