Skip navigation.
Home

Writing SLAPI plugins


Note that this article was written five years ago, and the code here will probably not work correctly with the new Fedora release of Directory Server. I will update this article and code examples as soon as I can possibly can, after I've finished playing with the DS installation on my dev-box.


Part 1: Overview of the API and Writing a Simple Plug-In

By writing a Netscape Directory Server extension ("plug-in"), you can extend the server's capabilities in a number of different ways. For example, you can validate data before it's stored in the directory, notify users when data in the directory changes, or replace the existing database with your own database. A plug-in is a shared object or library (or, on Windows NT, a dynamic link library) containing your own functions. Plug-ins are called by the server when specific events occur, such as before an LDAP add operation is performed.

Developing a Netscape Directory Server plug-in isn't as difficult as you might think. The plug-in API is rich in features and easy to learn and use, and it comes with excellent on-line documentation (XXX ToDo: Missing link Netscape Directory Server Plug-In Programmer's Guide). This article is the first in a series that will introduce you to the world of Netscape Directory Server plug-ins. In this article, I'll give you an overview of the plug-in API and also show you a small, but useful, example of a plug-in that performs regular expression filtering in entry modification requests. But first things first: I'll begin by asking you to consider whether a plug-in is really the best way to do what you want to do.

Do You Really Want to Write a Plug-In?

Plug-in functions can extend the capabilities of the Directory Server by enabling you to do the following:

  • validate data before the server performs an LDAP operation on it.
  • perform some action (that you define) after the server successfully completes an LDAP operation.
  • define extended operations (which are part of the LDAP v3 protocol).
  • provide alternate matching rules when comparing certain attribute values.
  • set up the server to use your own type of database for storing data.

There are some disadvantages to writing your own Directory Server plug-ins, though. A bug in a plug-in can crash your entire Directory Server, causing serious outages or even data corruption. Changes to or new versions of a server plug-in are hard to put into service, since to do so requires restarting all your servers. And your plug-in will most likely not work with an LDAP server from a different provider (although, OpenLDAP has partial support of SLAPI!).

If you think you need the functions a plug-in can provide but are daunted by the drawbacks, you might consider developing an alternative such as one of the following:

  • A standard LDAP client, using regular search, add, delete, or modify operations. Client development toolkits are available for C, Java, Perl, and many other languages.
  • An LDAP client using advanced search extensions, such as persistent searches.
  • An LDAP client that reads and understands theChangeLog of the Directory Server, perhaps in conjunction with persistent searches.

Lots of documentation is available on these solutions; XXX ToDo Provide links here, DevEdge is gone ... :-/

Overview of the Plug-In API

In this brief overview of the plug-in API, I'll describe:

  • the types of plug-ins you can write and when they're called by the Directory Server
  • how the server passes parameters to a plug-in and what some of those parameters might be
  • what one type of plug-in, called a pre-operation plug-in, returns to the server

I'll also list some of the more common pitfalls and gotchas to watch out for when you work with the plug-in API.

Types of Plug-Ins

The Directory Server has hooks that allow you to register your plug-in functions to be called when specific events occur. Here's a list of the kinds of plug-ins that you can register, noting when they're called:

  • Pre-operation plug-in functions are called before an LDAP operation is performed (for example, before a new entry is added or modified in the database). The main purpose of this type of plug-in is to validate data before it's used in an operation.
  • Post-operation plug-ins are called after an operation has completed (just before any result is returned to the LDAP client). This type of plug-in serves to invoke a function, such as sending notifications to users, when a particular operation is executed.
  • Entry storage plug-ins are called right before data is written to the default back-end database (LDBM) and entry fetch plug-ins are called just after data is retrieved from the default back-end database. These types of plug-ins can for instance be used to encrypt data before it's saved and decrypt it when it's read from a database.
  • Database plug-ins are called when the server is adding, modifying, removing, renaming, or searching for entries in the database. The point of this type of plug-in is to implement your own database back end to the server, replacing the default LDBM database.
  • Extended operation plug-ins are called when the client requests an operation by its OID (identifier).
  • Matching rule plug-ins are called when the client sends a search with an extensible matching search filter. These plug-ins filter search result candidates based on an attribute.
  • Syntax plug-ins are called when the server is getting a list of possible candidates for a search or adding or deleting values from certain attribute indexes. These plug-ins also define the comparison operations used in searches (for example, CES, CIS).

The most commonly used plug-ins are the pre-operation and post-operation types. Figure 1, from the online programmer's guide,shows how an LDAP client call flows through the server and your plug-ins.


Figure 1. Architecture of the Directory Server and serverplug-ins

Architecture SLAPI


How Parameters Are Passed to a Plug-In

Most plug-ins receive parameters in a data structure called a parameter block (specifically, the Slapi_PBlock data structure), which is passed by the server when it calls the plug-in. The parameters consist of data (name-value pairs) that's relevant to the operation being processed. For example, the parameter block for a pre-operation add function contains pointers to the target DN and the entry to be added, while the parameter block for a pre-operation bind function contains pointers to the DN of the user, the authentication method, and the user's credentials.

Your plug-in function should have the following prototype to ensure that the server passes it this data structure:

int plug-in_function(Slapi_PBlock pb);

To get the value of a parameter in the Slapi_PBlock structure, you call the slapi_pblock_get() function. To modify or set a certain parameter value in the parameter block, you call slapi_pblock_set(). These functions both take three arguments: a pointer to the parameter block, an argument ID, and a pointer to a variable where the value that you want to get or set is stored.

The argument ID is the ID of the name-value pair that you want to get or set. For a complete list of IDs that you can specify, see Chapter15 of the Netscape Directory Server Plug-In Programmer's Guide. Some IDs that we'll be using in our example plug-in are as follows:

  • SLAPI_PLUGIN_ARGC - Specifies the number of arguments passed to the plug-in (from the server configuration file).
  • SLAPI_PLUGIN_ARGV - Specifies the list of arguments passed to the plug-in (from the server configuration file).
  • SLAPI_PLUGIN_VERSION - Specifies the version of the plug-in API used by this plug-in. It currently can be SLAPI_PLUGIN_VERSION_01 or SLAPI_PLUGIN_VERSION_02.
  • SLAPI_PLUGIN_DESCRIPTION - Registers a description for the plug-in.
  • SLAPI_PLUGIN_PRE_MODIFY_FN - Registers a function to be called before a modify request is sent to the database back end.
  • SLAPI_PLUGIN_PRE_ADD_FN - Registers a function to be called before an LDAP add operation is performed.
  • SLAPI_MODIFY_MODS - Gets the LDAP modify structure, returning an LDAPMod handle.
  • SLAPI_ADD_ENTRY - Gets a structure containing the new entry to be added, returning a Slapi_Entry.

What a Pre-Operation Plug-In Returns to the Server

Plug-in functions that are invoked before an LDAP operation is performed can prevent the operation from being performed. They do this by returning either a 0 for success or a negative value for failure. If a failure code is returned, the LDAP operation in progress is stopped and an error is returned to the client.

You create the result code by calling slapi_send_ldap_result() . To return a failure code, set it to LDAP_CONSTRAINT_VIOLATION or another appropriate LDAP error. When an error happens, it might be appropriate to log an error message. Call the function slapi_log_error(), as in the following example:

slapi_log_error(SLAPI_LOG_PLUGIN, "test_plug-in",
                "Can't allocate memory, which is a Bad Thing.\n");

The first argument is the level of severity of the message, which corresponds to the Log Level setting selected in the Server Manager under Server Preferences | LDAP. If a Log Level setting is selected,messages with that severity level are written to the error log. You're free to use any of the levels available (listed in the >slapi_log_error() description), but I recommend using SLAPI_LOG_PLUGIN, which is typically used to identify messages from server plug-ins.

Common Pitfalls and Gotchas

There are a few important things to remember when using the plug-in API. The on-line documentation covers it all, but I would emphasize these general warnings and guidelines:

  • Make sure all functions and function calls are reentrant. The Directory Server is heavily multi-threaded, and your plug-in functions will be called from more than one thread in many cases.
  • Make sure you use the appropriate memory management routines and avoid memory leaks. This seems obvious, but even a very minuscule memory leak will make your Directory Server process grow out of bounds. You must use the native plug-in API memory allocation routines to make sure all memory blocks in the server are allocated on the same heap and managed by the same API. Instead of malloc()you want to use slapi_ch_malloc(), and instead of free() you must use slapi_ch_free().
  • Try to debug and test all possible branches and code segments of your plug-in code. Don't leave anything untested, even if it's likely that it will never be called.

Our Simple Plug-In - Regular Expression Filtering

The example I'm about to show you is a fairly simple, yet useful, pre-operation plug-in. It will enable you to accept or refuse requests to add or modify an entry, depending on the outcome of one or more filters that perform regular-expression matching. Some uses of this plug-in include:

  • making sure themailForwardingAddress attributes are set only to addresses within your local domain
  • enforcing syntax rules for attributes like telephone numbers, to ensure they all have a similar look.
  • limiting the set of values that an attribute can take (for instance, mailbox, native or program for the mailDeliveryOption attribute)

The plug-in is written to enable you to instantiate it multiple times. It will automatically handle this and build up an internal structure of the filters you want to activate.

I'll go through the source code for the plug-in so that you can see what it looks like. First, though, I'll explain how to configure the Directory Server to load the plug-in. For more information on writing and compiling plug-ins, see Chapter2 of the Netscape Directory Server Plug-In Programmer's Guide.

Configuring the Server

I wrote the example plug-in specifically for Netscape Directory Server 4.x, so this section covers how to configure server versions4.x and later to load the plug-in. For information on how to configure Netscape Directory Server 3.x for plug-ins, see Editing the Server Configuration Files in the Plug-In Programmer's Guide.

To specify that we're loading a plug-in, we need to add a plug-in directive to theslapd.ldbm.conf server configuration file. The plug-in directive starts with the word plugin, followed by a number of arguments. All the arguments need to be on the same line, and arguments that include white space must be enclosed in quotation marks (""). The arguments are as follows:

  1. The type of plug-in we're defining. The possible types, which correspond to those I listed earlier, are preoperation, postoperation, entrystore, entryfetch, database, extendedop, matchingrule, and syntax.
  2. Whether the plug-in is on or off by default. Note that in Directory Server 4.x you can turn the plug-ins on or off via the console as well, which will modify this parameter in the configuration file.
  3. A string identifying the plug-in.
  4. The full path to the library that implements the plug-in. This must be an absolute path, not a relative path.
  5. The name of the initialization function the server calls to register the plug-in. The name of our initialization function is rex_filter_init.
  6. Any additional arguments we want passed to the initialization function. In our example, we have three mandatory arguments being passed in: a comma-separated list of attributes we want to check the filter against; a 1 if the filter must match or a 0 if the filter must not match; and the regular expression we want to use as a filter.

To have multiple filters in our plug-in, we just need to make sure to instantiate the plug-in directive more than once. We're going to instantiate it four times, to serve four different purposes. Let's look at each of these instantiations. Please note that for readability, the plug-in directives are broken into two lines here,but in your code each directive must be on a single line.

plugin preoperation on "Check for mail domain" /usr/lib/rex_filt.so rex_filter_init
   mail,mailAlternateAddress 1 "^[-a-zA-Z0-9_]+@([-a-zA-Z0-9]+\.)*ogre\.com$"

This instantiation of the plug-in checks for an optional mail subdomain. The mail filter must match, and we check the filter against both the mail and the mailAlternateAddress attributes. The regular expression we want to use as a filter consists of:

  • a string beginning with letters, a hyphen, or an underscore (one or more of which must match), representing a user's mail name
  • an @ character to indicate we're moving on to the domain
  • one or more strings, followed by exactly one dot
  • the rest of the domain, which is ogre.com

A valid address matching this filter would be leif@smash.ogre.com, but not leif@smashogre.com or leif@smash..ogre.com.

plugin preoperation on "Check mail forwarding" /usr/lib/rex_filt.so rex_filter_init
   mailForwardingAddress 1 "(^[^@.]+$)|(^[^%!]+@([-a-zA-Z0-9]+\.)*ogre\.com$)"

This instantiation of the plug-in checks to see if there's a valid mail forwarding address, allowing users to forward email only within a certain mail domain (for security reasons). We check the filter against the mailForwardingAddress attribute. In the regular expression, the first grouping (within the first set of parentheses)looks for a string beginning with any character except for a dot or an at sign (@. This represents forwarding to a user name in the current domain. Then we've got an OR operation, indicated by the vertical bar (|. The second grouping matches any characters except for ! or %, which we leave out because those characters can fool the mail forwarding into changing the email-forwarding address. For instance, if we could get away with saying leif%netscape.com@ogre.com, this could allow us to forward email to leif@netscape.com, defeating the purpose of the filter. The rest of the grouping is the same as in the previous filter. Acceptable forwarding addresses that will match this filter includeleif and leif@ogre.com.

plugin preoperation on "Mail delivery options" /usr/lib/rex_filt.so rex_filter_init
   maildeliveryoption 1 "^(mailbox|n\ative|program)$"

This instantiation of the plug-in checks the mailDeliveryOption attribute for mailbox, native, or program, making sure the attribute follows the syntax supported by the Netscape/iPlanet Messaging Server. This prevents unexpected mail errors due to data inconsistency in the directory.

plugin preoperation on "Phonenumbers" /usr/lib/rex_filt.so rex_filter_init
   telephonenumber,pager 0 "[a-zA-Z]"

This last instantiation passes in a 0 instead of a 1, indicating that the filter must not match. It checks both the telephonenumber andpager attributes to make sure they contain anything but letters. This is somewhat of a stretch, but I wanted to show something different this time.

A Tour of the Source Code

Now we'll go through the actual source code for this plug-in. It consists of two files: one makefile and one C file,rex_filter.c. You'll need an ANSI C compiler, a make utility, and, of course, an installation of the Netscape (Fedora/Redhat now!) Directory Server to build the plug-in.

For now, this plug-in has only been tested and developed for Unix, particularly Linux and Solaris. I'll update the article later with support for Windows NT and possibly other platforms if there is interest. If you have problems (or success!) using any other systems, I'd appreciate feedback. Also, this article is a reprint from 5 years ago, and I haven't yet had time to verify that the latest distribution of the Directory Server behaves the same.

Listing 1 shows the makefile, which has been tested to work on Solaris and Linux only. You should modify the definitions in the beginning to match your build environment - for example, specifying the C compiler to use, the optimizer options, and so on. The DS definition should be set to the installation base of your Directory Server installation; the build process needs this to find the plug-in API #include files.


Listing 1
#######################################################################
# Build configuration is done here!
#
DS              = /server/server4/plugins/slapd/slapi
ETAGS           = etags

CC              = gcc
SH_OPTS         = -fpic

LD_OPTS         = -G
FLAGS           = -D_REENTRANT -Wall
OPT             = -O6 -DNDEBUG
#######################################################################
# You shouldn't have to touch anything below here.
#
INCLUDE         = -I. -I$(DS)/include
CFLAGS          = $(FLAGS) $(OPT) $(INCLUDE) $(SH_OPTS)
LDFLAGS         = $(LD_OPTS)

TARGETS         = rex_filt.so


#######################################################################
# Main targets.
#
all:            $(TARGETS)

rex_filt.so:    rex_filt.o
        $(LD) $(LDFLAGS) -o $@ rex_filt.o

clean:
        rm -f *.o core *.~ *~



The C source code is fairly long, but the actual "core" of the plug-in isn't very complicated. I'll go through the C source segment by segment, explaining key components.

The first segment, shown in Listing 2, begins with a number of #include and #define directives. The important #include here is the slapi-plug-in.h file, which contains all API prototypes, structures, and constants. We also define a number of string constants to be used for error logging during initialization and operation, and we define two main structures: Rex_Filter_Attrs (a linked list of attribute types, defining which attributes a particular filter should operate on) and Rex_Filter (a linked list of filter rules, one for each instantiation in the server configuration file). Finally, we declare a few global variables, the list of plug-in filters, the number of filters currently defined, and the description of this plug-in. The description is used only once when registering the filter, the first time the plug-in is invoked.


Listing 2
/*  -*- Mode: C; eval: (c-set-style "GNU") -*-
 */
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define LOG_FACILITY	SLAPI_LOG_PLUGIN
#define PLUGIN_NAME	"rex_filter"

#define ERR_NOMATCH	"An attribute does not match a server regex rule.\n"
#define ERR_MATCH	"An attribute matches a server regex rule.\n"

#define ERR_MALLOC	"Can't allocate memory, which is a Bad Thing(tm).\n"
#define ERR_NOMODS	"Could not get the modifications.\n"
#define ERR_NOENTRY	"Could not get entry.\n"

#ifdef __GNUC__
#  define INLINE inline
#else /* not __GNUC__ */
#  define INLINE
#endif /* __GNUC__     */

typedef struct berval BerVal;

typedef struct _Rex_Filter_Attrs
{
  char *type;
  char first;
  int len;
  struct _Rex_Filter_Attrs *next;
} Rex_Filter_Attrs;

typedef struct _Rex_Filter
{
  char *string;
  char *attributes;
  regex_t *regex;
  int match;
  Rex_Filter_Attrs *attrs;
  struct _Rex_Filter *next;
} Rex_Filter;

static int rex_num_filters = 0;
static Rex_Filter *rex_filter_list = NULL;
static Slapi_PluginDesc rex_descript = { PLUGIN_NAME,
					 "Leif Hedstrom",
					 "1.0",
					 "Regex filter plugin" };



In the next segment, shown in Listing 3, we declare a number of "helper" functions. These are used internally in the plug-in and during the initialization. Even though these functions are very important, we aren't going to investigate them thoroughly. We'll concentrate on the actual plug-in functions later. The helper functions are as follows:

  • free_attributes() - Frees memory used in a linked list of attributes. We call this to clean up after a fatal error has occurred.
  • parse_attributes() - Parses a comma-separated list of attribute types (names) and generates a linked list of attributes. We use this function in the initialization routine, when examining the arguments passed from the server configuration file.
  • list_has_attribute() - Tests whether aRex_Filter_Attrs list includes the specified attribute. This is used when evaluating the plug-in for a certain operation, to decide if a filter rule should be applied to the operation.
  • create_filter() - Creates a new filter, using arguments passed from the server configuration file.
  • loop_ber_values() - Loops through each value in the modify structure and applies the appropriate regular expression to each value. This function is used in the modify pre-operation function, when evaluating a modify request.


Listing 3
static int
free_attributes(Rex_Filter_Attrs *attrs)
{
  Rex_Filter_Attrs *cur;

  if (!attrs)
    return 0;

  while (attrs)
    {
      cur = attrs;
      attrs = cur->next;
      slapi_ch_free((void **)&cur);
    }

  return 1;
}

static Rex_Filter_Attrs *
parse_attributes(char *str)
{
  char *tmp;
  Rex_Filter_Attrs *cur, *ret;

  if (!str)
    return (Rex_Filter_Attrs *)NULL;

  tmp = str;
  while (*tmp)
    {
      *tmp = tolower((int)*tmp);
      tmp++;
    }
  if (!(ret = (Rex_Filter_Attrs *)slapi_ch_malloc(sizeof(Rex_Filter_Attrs))))
    return (Rex_Filter_Attrs *)NULL;

  cur = ret;
  tmp = strtok(str, ",");
  while (tmp)
    {
      if (!cur)
	{
	  free_attributes(ret);
	  return (Rex_Filter_Attrs *)NULL;
	}

      cur->type = tmp;
      cur->first = *tmp;
      cur->len = strlen(tmp);

      if ((tmp = strtok(NULL, ",")))
	cur->next = (Rex_Filter_Attrs *)
	  slapi_ch_malloc(sizeof(Rex_Filter_Attrs));
      else
	cur->next = (Rex_Filter_Attrs *)NULL;

      cur = cur->next;
    }

  return ret;
}

static INLINE int
list_has_attribute(Rex_Filter_Attrs *attrs, char *type)
{
  int len;

  if (!attrs || !type)
    return 0;

  len = strlen(type);
  while (attrs)
    {
      if ((attrs->first == *type) &&
	  (attrs->len == len) && (!strcmp(attrs->type, type)))
	return 1;

      attrs = attrs->next;
    }

  return 0;
}

static int
create_filter(Rex_Filter *filter, char *attributes, char *string)
{
  if (!filter || filter->attrs || !attributes || !string)
    return 0;

  if (!(filter->string = slapi_ch_strdup(string)))
    return 0;

  if (!(filter->attributes = slapi_ch_strdup(attributes)))
    {
      slapi_ch_free((void **)&(filter->attributes));
      return 0;
    }

  if (!(filter->attrs = parse_attributes(filter->attributes)))
    {
      slapi_ch_free((void **)&(filter->string));
      slapi_ch_free((void **)&(filter->attributes));
      return 0;
    }

  if (regcomp(filter->regex, filter->string, REG_EXTENDED|REG_NOSUB))
    {
      slapi_ch_free((void **)&(filter->attributes));
      slapi_ch_free((void **)&(filter->string));
      free_attributes(filter->attrs);
      return 0;
    }

  return 1;
}

static INLINE int
loop_ber_values(Slapi_PBlock *pb, Rex_Filter *filter, BerVal **bvals,
		char *type)
{
  int res;

  while (*bvals)
    {
      res = regexec(filter->regex, (*bvals)->bv_val, (size_t) 0, NULL, 0);
      if ((!res && !filter->match) || (res && filter->match))
	{
	  slapi_log_error(LOG_FACILITY, PLUGIN_NAME,
			  "Violation: %s /%s/ on '%s: %s'\n",
			  filter->match ? "require" : "refuse",
			  filter->string, type, (*bvals)->bv_val);
	  slapi_send_ldap_result(pb, LDAP_CONSTRAINT_VIOLATION, NULL,
				 filter->match ? ERR_NOMATCH : ERR_MATCH, 0,
				 (BerVal **)NULL);
	  return 1;
	}
      bvals++;
    }

  return 0;
}


The next code segment, shown in Listing 4, is the core function for handling add operations. It's called every time an LDAP add operation is initiated by a client. As described earlier, it takes one argument, which is a pointer to a parameter block. We use slapi_pblock_get() to retrieve the SLAPI_ADD_ENTRY argument, returning a Slapi_Entry structure, which holds the entire entry that will be added to the database.

After retrieving the Slapi_Entry structure, we loop through all filters that we've defined in the server configuration file. Each such filter has one regular expression and one or more attributes. We loop through every attribute type in the entry structure and test whether the filter should be applied to this attribute's value.

If the current attribute should be examined, we loop through each value of the attribute and apply the regular expression. We have to loop through all the BER values, since an attribute can have a single value or multiple values.

If a filter rule is matched, we return a negative value, -1,indicating that the add operation should be denied. If there's no match for any filters for any attribute values, we return 0. When the server gets the nonzero return value, it will continue, potentially call other plug-ins, and eventually commit the add operation to the database.


Listing 4
int
eval_add_filter(Slapi_PBlock *pb)
{
  Rex_Filter *filter;
  Rex_Filter_Attrs *attrs;
  Slapi_Entry *entry;
  Slapi_Attr *att;
  BerVal **bvals;

  if (!entry)
    return 0;

  if (slapi_pblock_get(pb, SLAPI_ADD_ENTRY, &entry) || !entry)
    {
      slapi_log_error(LOG_FACILITY, PLUGIN_NAME, ERR_NOENTRY);
      slapi_send_ldap_result(pb, LDAP_NO_MEMORY, NULL, ERR_NOENTRY, 0,
			     (BerVal **)NULL);
      return (-1);
    }

  filter = rex_filter_list;
  while (filter)
    {
      attrs = filter->attrs;
      while (attrs)
	{
	  if (!slapi_entry_attr_find(entry, attrs->type, &att))
	    {
	      slapi_attr_get_values(att, &bvals);
	      if (loop_ber_values(pb, filter, bvals, attrs->type))
		return (-1);
	    }
	  attrs = attrs->next;
	}
      filter = filter->next;
    }

  return 0;
}



The second plug-in function, shown in Listing 5, is called when an LDAP modify request is being handled. It works very similarly to the eval_add_filter() function, except we only have to deal with a limited number of modify requests. Using slapi_pblock_get(), we retrieve the SLAPI_MODIFY_MODS argument, returning anLDAPMod structure.

We loop through each modify item in the structure, and then loop through each value for the request. We reuse theloop_ber_values() function here, since it's identical to what we used for eval_add_filter(). And again, if a filter rule matches, we return -1 to prevent the server from accepting this modify request. If no filter matches, we return 0 and the server continues with the operation.


Listing 5
int
eval_mod_filter(Slapi_PBlock *pb)
{
  Rex_Filter *filter;
  LDAPMod **mods, *mod;

  if (!mods)
    return 0;

  if (slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods) || !mods)
    {
      slapi_log_error(LOG_FACILITY, PLUGIN_NAME, ERR_NOMODS);
      slapi_send_ldap_result(pb, LDAP_NO_MEMORY, NULL, ERR_NOMODS, 0,
			     (BerVal **)NULL);
      return (-1);
    }

  while ((mod = *mods))
    {
      if (!(mod->mod_op & LDAP_MOD_DELETE))
	{
	  filter = rex_filter_list;
	  while (filter)
	    {
	      if (list_has_attribute(filter->attrs, mod->mod_type) &&
		  loop_ber_values(pb, filter, mod->mod_bvalues,
				  mod->mod_type))
		return (-1);
	      filter = filter->next;
	    }
	}
      mods++;
    }

  return 0;
}



The final code segment, shown in Listing 6, is the most important function, to initialize the plug-in. This function can be called one or several times, from theslapd.ldbm.conf server configuration file.

We begin by requesting the list of arguments (from the slapd.ldbm.conf invocation) and the number of arguments. These two parameters,argv and argc, are used to create anew filter structure, by calling create_filter(). They have the same format as the regular command-line arguments, as passed to every C program's main() function. If we can parse the command-line argument properly, we'll link it into the list of regular expression filters for later use. Note that we're extremely careful handling errors during this process and always try to cleanup after ourselves before exiting.

If this is the first invocation to the initialization function, we also have to register our two pre-operation functions with the server. This is done using theslapi_pblock_set() function, with appropriate parameters. First we set the plug-in API version to SLAPI_PLUGIN_VERSION_01, which will allow this plug-in to work with Directory Server 3.x as well as 4.x. Next, we set the SLAPI_PLUGIN_DESCRIPTION structure, using the global rex_descript structure. This includes information like plug-in name and plug-in version, and is used by the Netscape Console. Finally, we add the SLAPI_PLUGIN_PRE_MODIFY_FN and SLAPI_PLUGIN_PRE_ADD_FN parameters so that the server will call our two plug-in functions for add and modify LDAP operations,applying our regular expression rules before accepting such operations.

If any of these calls to slapi_pblock_set() fails, we call slapi_log_error() to log an error message, and we return a -1 to tell the server that the initialization failed. If the initialization succeeds, which it usually will, we log an informational message using slapi_log_error() as well.


Listing 6
int
rex_filter_init(Slapi_PBlock *pb)
{
  char **argv;
  int argc;
  Rex_Filter *new, *last;

  if (slapi_pblock_get(pb, SLAPI_PLUGIN_ARGC, &argc) ||
      slapi_pblock_get(pb, SLAPI_PLUGIN_ARGV, &argv) || (argc < 3) || !argv)
    {
      slapi_log_error(LOG_FACILITY, PLUGIN_NAME,
		      "Can't locate plugin arguments, please panic!\n");
      return (-1);
    }

  if (!(new = (Rex_Filter *)slapi_ch_malloc(sizeof(Rex_Filter))))
    {
      slapi_log_error(LOG_FACILITY, PLUGIN_NAME, ERR_MALLOC);
      return (-1);
    }

  if (!(new->regex = (regex_t *)slapi_ch_malloc(sizeof(regex_t))))
    {
      slapi_log_error(LOG_FACILITY, PLUGIN_NAME, ERR_MALLOC);
      return (-1);
    }

  new->next = (Rex_Filter *)NULL;
  new->attrs = (Rex_Filter_Attrs *)NULL;
  new->match = (*(argv[1]) == '0' ? 0 : 1);
  if (!create_filter(new, argv[0], argv[2]) || !new->attrs)
    {
      slapi_ch_free((void **)&(new->regex));
      slapi_ch_free((void **)&new);
      slapi_log_error(LOG_FACILITY, PLUGIN_NAME,
		      "Can't make filter out of plugin arguments.\n");
      return (-1);
    }

  if ((last = rex_filter_list))
    {
      while (last && last->next)
	last = last->next;
      last->next = new;
    }
  else
    rex_filter_list = new;

  if (rex_num_filters++)
    return 0;

  if (slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01) ||
      slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&rex_descript) ||
      slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_MODIFY_FN,
		       (void *)&eval_mod_filter)
      || slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_ADD_FN,
			  (void *)&eval_add_filter))
    {
      slapi_log_error(LOG_FACILITY, PLUGIN_NAME,
		      "Error registering plugin functions.\n");
      return (-1);
    }

  slapi_log_error(LOG_FACILITY, PLUGIN_NAME,
		  "plugin loaded\n");

  return 0;
}



Short and Sweet

As you can see, the core code for our pre-operation plug-in isshort and sweet, though outside it we spend a lot of time checkingand testing result code from memory allocation routines and such. Sowithout writing a lot of code, we've developed a powerful and usefulNetscape Directory Server plug-in. A tar-ballwith the source is available from my FTP site. Stay tuned forfurther articles exploring the world of plug-ins.


Many thanks to Michelle Hedstrom for writing parts of this articleand encouraging me to finish it on time.