New LDAP C API

Michael B Allen mba2000 at ioplex.com
Tue Feb 13 23:05:06 GMT 2007


On Tue, 13 Feb 2007 10:45:21 -0800
Howard Chu <hyc at highlandsun.com> wrote:

> Andrew Bartlett and I were having a conversation about LDAP APIs and how 
> terrible the current one is, and the fact that the Samba team has 
> implemented their own. On the OpenLDAP Project we've had redesigning the 
> API on our TODO list for a number of years, but there was never enough 
> time/energy to get it done. It seems that perhaps now there may be 
> enough energy, at least, so I've dug up our old list of API flaws and 
> posted it here http://scratchpad.wikia.com/wiki/LDAP_C_API
> 
> I've already invited folks from the Fedora Directory Server team to 
> participate in this new design, and I'd like to get input from you folks 
> too. Anybody who's done a significant amount of work with LDAP has 
> probably cursed the API more than a few times. Let's fix it.

I just went through a lot of LDAP API related work so FWIW I think I
can add some comments about this.

One thing that we did that I would really like to see in a new and
improved API is allowing the user to set "attribute definitions". In
our API we have a function with the following prototype:

  int ldapx_set_attrdef(struct ldapx *lx,
          const unsigned char *name,
          int type,
          uint32_t flags,
          int conv);

By default everything is multi-valued binary just like the ldap_* API but
if you set an attribute definition you can specify that it's a single
valued string or an integer or whatever. This allows applications to
do things like convert a string from UTF-8 to the locale encoding and
represent the element as a single valued element.

But something even more useful about being able to set attribute
definitions is allowing setting a "conversion sepcifier". For example,
if we do the following:

  ldapx_set_attrdef(lx,
          "lastLogon",
          LDAPX_TYPE_INT64,
          LDAPX_SINGLE_VALUED,
          LDAPX_CONV_TIME1970M_X_TIME1601);

then the LDAPX_CONV_TIME1970M_X_TIME1601 means that at the API level
all instances of lastLogon will be represeted as milliseconds since 1970
UTC but at the storage level (in the DIT) they are nanos since 1601.

Our "ldapxsearch" program illustrates the effect of this very well as
you can see there are a number of things that have been automatically
converted; sids, binary data, dates, etc:

  $ ldapxsearch 'ldap:///DefaultNamingContext??sub?(cn=Michael B Allen)'
      [CN=Michael B Allen,CN=Users,DC=example,DC=com]
        objectClass: user
        objectClass: organizationalPerson
        objectClass: person
        objectClass: top
        cn: Michael B Allen
        sn: Allen
        givenName: Michael B
        distinguishedName: CN=Michael B Allen,CN=Users,DC=example,DC=com
        instanceType: 4
        whenCreated: 2006-06-20 15:17:27
        whenChanged: 2006-06-20 15:17:27
        displayName: Michael B Allen
        uSNCreated: 49183
        uSNChanged: 49188
        name: Michael B Allen
        objectGUID:: skGU1YuZ70u9BYcQyYU0Rw==
        userAccountControl: 66048
        badPwdCount: 0
        codePage: 0
        countryCode: 0
        badPasswordTime: 2006-11-21 17:10:00
        lastLogoff: 0
        lastLogon: 2007-01-19 11:51:37
        pwdLastSet: 2006-06-20 15:17:27
        primaryGroupID: 513
        objectSid:: S-1-5-21-4133388617-793952518-2001621813-1129
        accountExpires: 0x7fffffffffffffff
        logonCount: 132
        sAMAccountName: miallen
        sAMAccountType: 805306368
        userPrincipalName: miallen at example.com
        objectCategory: CN=Person,CN=Schema,CN=Configuration,DC=example,DC=com
  Success: 1 objects / 32 attributes

Anyway, regarding your specific "gripes" ...

> 1. Needs an explicit library_init/library_destroy and a
> library_handle. (No static/global state, everything referenced by the
> library_handle.)

I not sure I really understand this but when it comes to API
initialization my personal feeling is that a well designed API
always has a top level context object. An example of a bad API is
malloc/realloc/calloc/free. An example of a good API is krb5_*. The
context object should be used to hold anything that might need to be
initialized by the library although I think it is ok to have static data
for certain things (e.g. error tables, character conversion contexts,
etc).

So you might have something like the following (assuming a namespace
of ldap2):

  struct ldap2_context *ctx;
  int ret;
 
  ret = ldap2_init_context(&ctx);
  if (ret < 0) {
      // error
      return ret;
  }
  // use ctx ...

>      1. the API is too malloc-happy. result parsing etc. should be done
>      in-place as much as possible.

Given the highly variable nature of LDAP responses, it's not clear to me
how you could get around this. However I do find the memory allocation
API very annoying. I would hope that could be normalized a little.

  ldap_memfree
  ber_free
  ldap_value_free
  ldap_value_free_len
  ...

> 5. "proper" error handling
> 
>      1. Any routine which can "fail" (e.g., most) should return a clear
>      indication of success or the nature of the failure. ("fail" here
>      refers to API failure, not a protocol failure.)
> 
>      2. Protocol errors are not to be treated as API errors.

This may not be directly related, but with respect to API design, I
prefer to see every function return an integer that is less than 0 to
indicate an error. For example, instead of ldap_get_dn returning char *
it should return int and have a char ** out parameter or char *buffer
in which the result could be returned.

>      3. no ld_errno, no ld->ld_errno, etc.. (This relates to Howard's #3.)

I actually don't think the ldap_get_option method is all that bad. At
least calling a function to get the error result is perfectly reasonable.

> 10. ldap_* be a low-level LDAP API (one-to-one mapping of API to
> protocol). Higher level "directory" API provided above to handle referral
> chasing and such.

As I somewhat eluded to before, I pretty much just did this. I created a
"low-level" api that *just* barely abstracted the ldap_* API so that I
could use alternative stores (e.g. an mmaped file). Then I have a higher
level API that is more user friendly.

Just to give you an idea of what we use, here are some example protos
from our "high-level" API:

  struct ldapx_object *ldapx_object_new(struct allocator *al,
  			const unsigned char *dn,
  			int flags); 
  int ldapx_object_del(struct allocator *al, struct ldapx_object *obj);
  int ldapx_object_del_all(struct allocator *al, struct ldapx_object *objs); 
  int ldapx_get_objects(struct ldapx *lx,
              struct ldapx_search_params *params,
              struct ldapx_object **objs, 
              struct allocator *al);
  int ldapx_object_mod(struct ldapx *lx,
              struct ldapx_object *obj,
              int mod_op, 
              const unsigned char *name,
              const void *val,
              int flags,
              struct allocator *al);
  int ldapx_object_add(struct ldapx *lx, struct ldapx_object *obj);
  int ldapx_object_modify(struct ldapx *lx, struct ldapx_object *obj);

For example, to do an "add" would look something like:

  struct ldapx_object *obj;

  obj = ldapx_object_new(lx->al, dn, 0); 
  if (obj) { 
      if (ldapx_object_mod(lx,
                      obj,
                      LDAPX_MOD_ADD,
                      "someAttribute",
                      val,
                      0,
                      lx->al) == 0) { 
          ret = ldapx_object_modify(lx, obj);
      }
      ldapx_object_del(lx->al, obj);
  }

  if (ret < 0) {
      // error
      return ret;
  }
  // success

> What namespaces will we use?

Please don't use ldapx_*.

Mike

-- 
Michael B Allen
PHP Active Directory SSO
http://www.ioplex.com/


More information about the samba-technical mailing list