| < Day Day Up > |
|
The C-language API is the only one (at the time of the writing) being documented in the collection of RFCs. You will find it as RFC 1823, "The LDAP Application Program Interface." This RFC describes the API for LDAP (v2). The API for LDAP (v3) is still in draft form (draft-ietf-ldapext-ldap-c-api-05.txt) and is available from the Internet Engineering Task Force (IETF) Web site (http://www.ietf.org).
You can get an SDK from various vendors, some even for free, for example from the Web sites of Netscape, Novell, or Microsoft. With the OpenLDAP distribution, you also get the C libraries, which we will use for the examples in this chapter.
Nearly all function calls exist in two versions: synchronous and asynchronous. The synchronous function calls are postfixed with _s, as seen in the following example for the search functions:
ldap_search is the asynchronous function
ldap_search_s is the synchronous counterpart
Because synchronous and asynchronous function calls act differently, they do not deliver the same result codes. The synchronous functions return a result code indicating success or failure. The asynchronous functions, however, cannot report success or failure because they report immediately, without knowing whether the function succeeded. Instead, the asynchronous functions return the message ID of the operation that has been initiated. This message ID can be used later, for example to give the user the option of interrupting the function if it should take too much time (possibly a search function reporting too many results).
When compiling the code, remember to include the correct files and link them to the correct libraries of LDAP functions before compiling. Which libraries and files you have to include depends heavily on the platform you are working on and the particular SDK you are using. On UNIX systems, be sure that your compiler can find the dynamic libraries. On Solaris, you include the path the compiler needs to search using the LD_LIBRARY_PATH environment variable. Hewlett Packard uses SHLIB_PATH instead. The LDAP libraries need the nsl and socket libraries (-lnsl, -lsocket). [2] If you get an error message complaining about a symbol named "inet_aton," you will have to compile in the "resolv" library (-lresolv), too. This depends on the platform you are working on.
If you have any problems, look at the documentation shipped with the SDK you are using. Exhibit 46 shows the makefile on a Sun Solaris box. The makefile saves typing if you have to debug and recompile your programs. It is also useful as future documentation in case you ever need to know exactly what it was that you did when you compiled this piece of code successfully.
# Example Makefile to compile LDAP programs EXTRALDFLAGS=-lsocket -lnsl -lresolv LDAPLIB=-lldap -llber INCDIR=/usr/local/include LIBDIR=/usr/local/lib LIBS=-L$(LIBDIR) $(LDAPLIB) $(EXTRALDFLAGS) CFALGS=-I$(INCDIR) CC=gcc PROGS=Example1 all: $(PROGS) Example1: Example1.o $(CC) -o Example1 Example1.o $(LIBS) clean: /bin/rm -f $(PROGS) *.o a.out core
As mentioned previously, the LDAP C API is standard (informal RFC) for LDAP (v2). Briefly, here are the differences between LDAP (v2) and LDAP (v3):
LDAP (v3) clients can follow referrals and references. They will automatically follow them unless, via the ldap_set_option, the standard value is not overwritten.
Character encoding of the distinguished name and string values are encoded using UTF-8 encoding in v3. In v2, SDK US-ASCII or T.61 encoding is used
With LDAP (v3), you have to enable v3 with the ldap_set_option call. For compatibility reasons, the default value is v2.
Let us have a look at a simple LDAP example borrowed from RFC 1823 with some modification. The program retrieves the attributes of a number of entries. The only thing that you have to do is to adapt it to your needs. If you are still using the examples from Chapter 1, you can use the program in Exhibit 47 without any modification. As you see, there is nothing special about the program. You will recognize the main steps as described in the introduction:
ldap_open(HOSTNAME, LDAP_PORT): Opens the connection to the server
ldap_simple_bind_s(ld, NULL, NULL): Authenticates, in this case as "anonymous"
ldap_unbind(ld): Ends the session
#include <stdio.h> #include <ldap.h> /* Specify the parameters here. */ #define HOSTNAME "localhost" #define PORTNUMBER 389 #define BASEDN "o=ldap_abc.de" #define SCOPE LDAP_SCOPE_SUBTREE #define FILTER "(sn=Parker)" main() { LDAP *ld; LDAPMessage *res, *e; int i; char *a, *dn; void *ptr; char **vals; /* open a connection */ if ((ld = ldap_open(HOSTNAME, LDAP_PORT)) == NULL) printf("Error connecting to %s\n",HOSTNAME); exit(1); /* authenticate as nobody */ if (ldap_simple_bind_s(ld, NULL, NULL) != LDAP_SUCCESS) { ldap_perror(ld, "ldap_simple_bind_s"); exit(1); } /* search for entries using FILTER and BASEDN */ if (ldap_search_s(ld, BASEDN, LDAP_SCOPE_SUBTREE, FILTER, NULL, 0, &res)!= LDAP_SUCCESS) { ldap_perror(ld, "ldap_search_s"); exit(1); } /* step through each entry returned */ for (e = ldap_first_entry(ld, res); e != NULL; e = ldap_next_entry(ld, e)) { /* print its name */ dn = ldap_get_dn(ld, e); printf("dn: %s0, dn); free(dn); /* print each attribute */ for (a = ldap_first_attribute(ld, e, &ptr); a != NULL; a = ldap_next_attribute(ld, e, ptr)) { printf("attribute: %s0, a); /* print each value */ vals = ldap_get_values(ld, e, a); for (i = 0; vals[i] != NULL; i++) { printf("value: %s0, vals[i]); } ldap_value_free(vals); } } /* free the search results */ ldap_msgfree(res); /* close and free connection resources */ ldap_unbind(ld); }
Note the iteration through the results set, accomplished here with a "for" loop.
Now let us have a closer look at the API, beginning with the structures used and then moving on to review some of the available functions. Note that we will not cover all of the available functions here. For more information, have a look at the standards definition in RFC 1823 or in the draft for LDAP (v3). A look at the documentation shipped with the software you are using is also helpful.
The SDK uses several structures to put in results and error messages. SDKs for both v2 and v3 of LDAP recognize the following structures:
LDAP structure: A structure returned by the ldap_open call
LDAP message structure: Used to return entry, reference, result, and error information
BerElement structure: Used to hold data and state information about encoded data
BerValue structure: Used to represent arbitrary binary data and its fields
Timeval structure: Used to represent an interval of time and its fields
For more information, have a look at the standards definition in RFC 1823 or in the draft for LDAP (v3). A look at the documentation shipped with the software you are using is also helpful.
The API offers a number of different functions that could be grouped as follows:
Authentication and control operations, such as bind, unbind, abandon
Interrogation operations, such as search and compare
Iteration commands through results sets
Update operations, such as add, delete, modify DN, modify
Let us have a look at each group.
In Exhibit 47, we saw an example of one of these types of operations, the bind function. Now let us complete the picture. Like any LDAP API, we first have to establish a connection to the server LDAP is running on. In Exhibit 47, the connection is opened by the following line:
if ((ld = ldap_open(HOSTNAME, LDAP_PORT)) == NULL)
This not only opens a connection to the server, but also allocates resources needed for subsequent actions. In the case of the C language, the "open" call returns an LDAP structure if it is successful or reports NULL if it is not. The LDAP structure returned by the ldap_open call, also called "connection handle," identifies this connection and must be used in subsequent calls on behalf of this connection.
The next step is to authenticate against the LDAP server. This is done using the ldap_bind function. Again, let us look at the example in Exhibit 47:
if (ldap_simple_bind_s(ld, NULL, NULL) != LDAP_SUCCESS)
In this example, we connect to the LDAP server as an anonymous user. It is also possible to connect with a userID and password. Regarding authentication, LDAP (v2) and LDAP (v3) are different. Both LDAP (v2) and LDAP (v3) C APIs offer simple authentication, i.e., the user authenticates with userID and password. LDAP (v2) APIs should furthermore offer SSL and Kerberos authentication. LDAP (v3) APIs (in draft status at the time of this writing) instead use SASL and consider Kerberos as deprecated. Look at the documentation of your LDAP SDK for further details.
The syntax for a simple asynchronous bind is:
int ldap_simple_bind( LDAP *ld, const char *dn, const char *passwd );
For a synchronous bind, the syntax is:
int ldap_simple_bind_s( LDAP *ld, const char *dn, const char *passwd );
Both approaches express the type of authentication (simple) in the function name. It is also possible to specify the type of authentication as a parameter in the bind call:
int ldap_bind(LDAP *ld, char *dn, char *cred, int method);
In LDAP (v2), the available methods are:
LDAP_AUTH_SIMPLE
LDAP_AUTH_KRBV41
LDAP_AUTH_KRBV42
In LDAP (v3), we have:
LDAP_SASL_SIMPLE (i.e. NULL)
text string identifying the SASL method
After the successful bind call, you can ask the LDAP server to perform actions upon the directory. When you have finished, you close the connection using ldap_unbind():
ldap_unbind(ld);
In the previous example, for both anonymous connections and authenticated ones, the unbind call takes only the connection handle as a parameter.
The control function "ldap_abandon()" abandons a particular request. The syntax is:
int ldap_abandon(LDAP *ld, int msgid);
where "msgid" is the identifier of the abandon operation.
As specified by the functional model (see Chapter 3), the C LDAP API recognizes two interrogation operations:
ldap_search: Searches for an entry and returns a message structure. This message structure is used in subsequent function calls to iterate through the results set, to get particular entries, or to operate one of the modify functions on it.
ldap_compare: Searches in the directory to determine whether, within a particular distinguished name, an attribute has the required value.
First, we see an example of the ldap_search function call. From the example in Exhibit 48, we learn the usage of the ldap_search function and discover a framework for writing a customized LDAP search tool. The command-line tools differ from implementation to implementation. As noted previously, if you are not happy with the command-line tools of your implementation, you can always create your own.
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <getopt.h> #include "ldap.h" static char *base = NULL; static char *binddn = NULL; static char *ldaphost = NULL; static int ldapport = 0; static struct berval passwd = { 0, NULL }; static char *progname = NULL ; main (int argc, char **argv) { LDAP *ld ; int scope, rc, parse_rc = 0; int version=-1; int i ; char **attrs=0; char *param, *filtpattern ; char *pwd ; progname = strdup(argv[0]); /* First we read the options of the program call: */ while ((i = getopt(argc, argv, "b:D:h:p:s:w:")) != EOF) { switch(i) { case 'b': /* search base */ base = strdup(optarg); break; case 'D': /* base dn */ binddn = strdup(optarg); break; case 'h' : /* host */ ldaphost = strdup(optarg); break; case 'p' : /* port */ ldapport = atoi(optarg); break; } case 's': /* scope */ if (strcasecmp(optarg, "base") == 0) { scope = LDAP_SCOPE_BASE; } else if (strncasecmp(optarg, "one", sizeof("one")- 1) == 0) { scope = LDAP_SCOPE_ONELEVEL; } else if (strncasecmp(optarg, "sub", sizeof("sub")- 1) == 0) { scope = LDAP_SCOPE_SUBTREE; } else { fprintf(stderr, "scope should be base, one, or sub\n"); } case 'w': passwd.bv_val = strdup(optarg); { char* p; for(p = optarg; *p != '\0'; p++) { *p = '\0'; } } passwd.bv_len = strlen(passwd.bv_val); break; default: fprintf(stderr, "%s: unrecognized option -%c\n", PROG, optopt); } }
To minimize the amount of code, the example in Exhibit 48 does not contain all possible switches. However, the example does allow you to specify the search filter and the attributes that the search should return. The allowed switches include the host, the port-number, the user credentials (dn + uid), and the scope.
The example in Exhibit 48 has two parts: the framework reading the parameters and the real search function implemented in C. Of course, the example does not include all of the control mechanisms that you will need in real life, but it is not difficult to add these mechanisms once you understand the program logic.
The first part of Exhibit 48 parses the switches that the user typed in the command line when the program was executed. This portion of the program gets the options from the program invocation. Regarding LDAP, there is nothing remarkable to say about it. The heart of the program is the "getopt" function call. Have a look at the documentation if you are not fluent in its usage. The password uses a structure called "berval". This structure contains both the length of the password and the password itself (see Exhibit 48).
In Exhibit 49 we construct the filter. The program interprets the string following the options (if any) as a filter. If the user did not supply a filter, the program sets a reasonable value (objectclass = *), i.e., any object class.
/* Here we get the filter, if the user did not * specify nothing we suppose she want to see * all objectclasses, i.e. objectclass=* */ if ((argc - optind < 1) || (*argv[optind] != '(' /*')'*/ && (strchr(argv[optind], '=') == NULL))) { filtpattern = "(objectclass=*)"; } else { filtpattern = strdup(argv[optind++]) ; } if (argv[optind] != NULL) { attrs = &argv[optind]; }
Now, finally, we get to the LDAP (see Exhibit 50). We open the connection to the server that the directory is running on. If the user does not specify anything, then localhost and port 389 are assumed. The set_option function sets the default version to 3. You can easily implement a new switch -v to allow the user to set the version. At the end, we authenticate against the directory server using simple bind. We could also have written the bind command in a different format, as shown in Exhibit 51.
if ((ld = ldap_init(ldaphost, ldapport)) == NULL) { perror("ldap_init"); } if (version == -1) { version = LDAP_VERSION3; } if (ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version) != LDAP_OPT_SUCCESS) { printf("Could not set LDAP_OPT_PROTOCOL_VERSION %d\n",version); exit(1); } printf("Binding with: %s and %s\n",binddn, passwd.bv_val); if (ldap_simple_bind_s(ld, binddn, passwd.bv_val) != LDAP_SUCCESS) { ldap_perror(ld, "ldap_bind"); exit(1); }
authmethod = LDAP_AUTH_SIMPLE ; if (ldap_bind_s(ld, binddn, passwd.bv_val, authmethod) != LDAP_SUCCESS) {
Now we can execute the search. Exhibit 52 shows the function call that does the job. In the next section, when we learn how to use the result structure, we will extend this search utility to print out the results found in the directory.
rc = ldap_search_s(ld, base, scope, filtpattern, attrs, attrsonly, res); if (rc != LDAP_SUCCESS) { fprintf(stderr, "ldap_search_s: %s\n",ldap_err2string(rc)); return(rc); }
Let us continue with the command-line search tool we began using in the previous section. Until now, we have been getting an error message when the search is not successful, a discouraging result when using a search command-line tool. The search function delivers a structure that lets us access the entries matching the search filter. The structure is the second of the two structures mentioned in the introduction: the message structure. The message structure delivers the error code and converts the error code into a human-readable message, thus allowing the user to analyze eventual errors. Moreover, it also allows us to iterate through the results set, to count the entries contained in the results set, to sort the entries, and to fetch the single entries.
The first thing we will do is to iterate through the results set, printing out each distinguished name of the entries found. Exhibit 53 shows the code you must attach after the search. It is fairly simple. Now let us produce professional output using the code shown in Exhibit 54. We simply attach this piece of code at the line printing the distinguished name, and we get the same output we received from the command-line tool from our software distribution. The only difference is that now we can conveniently adapt it to our personal preferences.
printf("Found %d entries matching the search\n", ldap_count_entries(ld,res)) ; for (e= ldap_first_entry(ld,res) ; e != NULL ; e = ldap_next_entry(ld,e)) { printf("DN: %s\n",ldap_get_dn(ld,e)); }
for (attr = ldap_first_attribute(ld, entry, &ber) ; attr != NULL ; attr = ldap_next_attribute(ld, entry, ber)) { if ((vals = ldap_get_values(ld, entry, attr)) != NULL) { for (i=0; vals[i] != NULL ; i++) { printf("\t%s: %s\n",attr,vals[i]); } /* release memory allocated for the vals array */ ldap_value_free(vals); } /* release memory allocated for the single attribute */ ldap_memfree(attr); } /* release memory allocated for the ber structure */ if (ber != NULL) { ber_free(ber,0); }
Let us begin with the easiest case of all, deleting an entry. The only thing we need is the distinguished name of the entry to he deleted:
dn = strdup(argv[optind++]); rc = ldap_delete_s(ld,dn);
We get the DN from the argument in the program invocation. The modifydn call is also not very complicated.
The add and modify functions both use the structure "ldapmod." Let us have a brief look at the structure:
typedef struct ldapmod { int mod_op; char *mod_type; union mod_vals_u { char **modv_strvals; struct berval **modv_bvals; } mod_vals; } LDAPMod; #define mod_values mod_vals.modv_strvals
The structure holds three pieces of information:
The type of modification to be applied
The attribute affected
The values that should be assigned to the attribute (this could be a pointer to an array of strings or a pointer to an array of binary data)
The syntax of the add function is:
int ldap_add_s( LDAP *ld, const char *dn, LDAPMod **attrs );
The syntax of the modify function is:
int ldap_modify_s( LDAP *ld, const char *dn, LDAPMod **mods );
In both function calls, ld is the connection handle and DN is the distinguished name. The third parameter is an array of LDAPMod structures.
Following are two examples: The first adds an entry to the directory, and the second executes some modification to an existing entry. In both cases, we have to populate the array of LDAPMod structures. In real life, you would read the data in from a file. However, because we are concentrating on the LDAP code, we fill the example array by hand to show you what it looks like.
For every attribute, we have to construct the structure like this:
/* 9 attributes + NULL as last value: LDAPMod *attributes[10] ; /* the single attributes: LDAPMod attribute1, attribute2, .... /* array for the first attribute values, NULL terminated. char * objectclass_vals = { "top", "person", "organizationalPerson", "inetOrgPerson", NULL } ; char * cn_vals = {"James T. Kirk"} ; /* here we repeat for every attribute . . . attribute1.mod_op = LDAP_MOD_ADD ; attribute1.mod_type = "objectclass" ; attribute1.mod_values = objectclass_vals ; attribute2.mod_op = LDAP_MOD_ADD ; /* here we repeat for every attribute . . . attributes[0] = &attribute1 ; attributes[1] = &attribute2 ; . . . attributes[9] = NULL ;
The above array is for the ldap_add operation. The array for ldap_modify operations is similar. The only difference is that the mod_op variable would be changed to either "replace" or "delete." The function call is straightforward. For "add," the function call is:
rc = ldap_add_s (ld, DN, attributes) ;
For "modify," the function call is:
rc = ldap_modify_s (ld,DN, attributes) ;
The modify distinguished name (or, more precisely, modify relative distinguished name) operation does not require such a complex structure. However, the syntax depends on which LDAP version you are using. Note that LDAP (v2) does not offer the possibility of moving the entry inside the directory information tree. Here is the syntax for LDAP (v2) for a synchronous function call:
int ldap_modrdn_s(ld, dn, newrdn) LDAP *ld; char *dn, *newrdn; int ldap_modrdn2_s(ld, dn, newrdn, deleteoldrdn) LDAP *ld; char *dn, *newrdn; int deleteoldrdn;
For asynchronous calls, simply delete the trailing "_s" from the LDAP commands. The second function deletes the old entry.
The situation is somewhat different in LDAP (v3). For synchronous calls, the syntax is:
int ldap_rename_s( LDAP *ld, const char *dn, const char *newrdn, const char *newparent, int deleteoldrdn, LDAPControl **serverctrls, LDAPControl **clientctrls );
For asynchronous calls, the syntax is:
int ldap_rename( LDAP *ld, const char *dn, const char *newrdn, const char *newparent, int deleteoldrdn, LDAPControl **serverctrls, LDAPControl **clientctrls, int *msgidp );
In this section we have seen the LDAP C API. This API offers a vast set of functions, and it is not possible to list all the possibilities. At the time of this writing, only LDAP (v2) is covered by an official standard. The LDAP (v3) C API is still in draft form, although it may well be a valid RFC by the time you read this book. If you want to see some good working examples of the C API, take the OpenLDAP distribution and look at its implementation of the command-line tools. You will really learn a lot. Furthermore, these examples will enable you to customize and implement your own command-line tools.
[2]The nls library provides national language support; the socket library is the interface.
| < Day Day Up > |
|