Sun Java System Directory Server Enterprise Edition 6.2 Developer's Guide

Part I Directory Server Plug-In API Guide

This part demonstrates how to develop Directory Server plug-ins. This part also explains what has changed since the last release, so you can upgrade existing plug-ins for use with Directory Server Enterprise Edition 6.2.

Chapter 1 Before You Start Writing Plug-Ins

This chapter helps you to decide whether to implement a server plug-in. It also explains what to do next if you choose to implement a plug-in.

This chapter covers the following topics:

When to Implement a Server Plug-In

Many Sun Java System Directory Server native product features rely on server plug-ins. Plug-ins are libraries of functions that are registered with the server to perform key parts of specific directory service operations.

The Directory Server plug-in API provides an interface to add server capabilities that are not in the current releases of the product. If you must add server capabilities because a required feature has not yet been implemented, the plug-in API might render that enhancement possible. If you can instead use standard features of the product, avoid creating a plug-in.

Maintaining Plug-Ins

Be aware that creating your own plug-in links your software solution to a specific product release. The plug-in API can evolve from release to release to accommodate new features. As you choose to upgrade to take advantage of new features, you might need to update your Directory Server plug-ins.

You can use the header files with class libraries solely to create and distribute programs to interface with the Software’s APIs. You can also use libraries to create, then distribute program “plug-ins” to interface with the Software’s plug-in APIs. You cannot modify the header files or libraries. You acknowledge that Sun makes no direct or implied guarantees that the plug-in APIs will be backward-compatible in future releases of the Software. In addition, you acknowledge that Sun is under no obligation to support or to provide upgrades or error corrections to any derivative plug-ins.

When providing technical support, Sun technical support personnel request that you reproduce the problem after turning your custom plug-ins off.

Sun Services

Sun Services can help you make the right deployment decisions. Sun Services can also help you to deploy the right solution for your specific situation, whether or not the solution includes custom plug-ins.

How Plug-Ins Interact With the Server

All client requests to Directory Server instances undergo particular processing sequences. The exact sequence depends on the operation that the client has requested. Yet overall, the sequences are similar. The way plug-ins handle requests is independent of how the client encodes the request. In other words, client request data appears essentially the same to plug-ins regardless of the client access protocol used to communicate with Directory Server.

In general, client request processing proceeds as follows. First, the Directory Server front end decodes the request. The core server handles the decoded request, calling the necessary back end functions to find or store entries, for example. The primary function of the Directory Server back end is to retrieve and store entries. Unless abandoned, however, a client request results in a response originating in the back end. The front end formats the response and sends it to the client.

The following figure illustrates how client request data moves through Directory Server.

Figure 1–1 Client Request Processing

Diagram shows how Directory Server receives, processes,
and responds to a client application request.

As a rule, plug-ins register functions to be called by Directory Server at specific steps of processing the client request. A plug-in has a specific type, such as preoperation or postoperation. A preoperation plug-in registers functions that handle incoming requests before the requests are processed in the server. For example, a preoperation plug-in can register a pre-bind function that is called before the core server begins processing the client bind. The pre-bind function might redefine how the client authenticates to Directory Server, enabling authentication against an external database. A postoperation plug-in registers functions that are called after a request is processed. For example, a postoperation plug-in might register a post-modification function that is called to log the information about the modification request. Example Uses provides examples for other plug-in types.

The two internal operation plug-in types form an exception to the rule that functions are called to perform client request processing. Internal operation plug-ins implement functions that are triggered for internal Directory Server operations. Internal operations are initiated not by client requests, but by Directory Server or by another plug-in called by Directory Server. Exercise caution when manipulating internal operation data.

The following table summarizes when Directory Server calls documented plug-in function types.

Table 1–1 Server Plug-In Types

Plug-In Type 

When Called 

Entry store-fetch 

Immediately before writing a directory entry to or immediately after reading a directory entry from the directory database 

type: ldbmentryfetchstore

Extended operation 

For processing an LDAP v3 extended operation 

type: extendedop

Internal postoperation 

After completing an operation initiated by Directory Server, for which no corresponding client request exists 

type: internalpostoperation

Internal preoperation 

Before performing an operation initiated by Directory Server, for which no corresponding client request exists 

type: internalpreoperation

Matching rule 

When finding search result candidates based on an extensible match filter for search operations only 

type: matchingrule

Object 

Depends on the functions that are registered. (This type is generic and is used to register other types) 

type: object

Password check 

When performing a strong check on a userPassword value to add or modify

type: passwordcheck

Password storage scheme 

When storing an encoded userPassword value that cannot be decrypted

type: pwdstoragescheme

Postoperation 

After completing an operation requested by a client 

type: postoperation

Preoperation 

Before performing an operation requested by a client 

type: preoperation

The following figure illustrates where in the processing sequence Directory Server calls some of the plug-in types described in Table 1–1.

Figure 1–2 Plug-In Entry Points

Diagram identifies points in client request processing
where plug-ins may be called.

Notice that postoperation return codes do not affect Directory Server processing.

The plug-in type and plug-in configuration information are typically specified statically in a configuration entry.

You can include multiple plug-ins in a single shared library, but you must specify individual, server-specific configuration entries for each plug-in that is registered statically. This means that if you want preoperation and postoperation functions, you typically write two plug-ins. Then you register each plug-in with Directory Server. Both plug-ins can be contained in the same shared library. Both plug-ins can communicate private data across the same operation or connection, as well, using the interface provided. You can also write one plug-in that initializes other plug-ins, if that configuration is required for your deployment.

The sample plug-ins delivered with the product follow the principle of building all plug-ins into one library, then registering plug-ins individually.

The following figure shows multiple plug-ins that reside in the same library.

Figure 1–3 Multiple Plug-Ins in a Shared Library

Diagram shows multiple plug-ins of different types may
be linked in a single shared library.

Each plug-in includes a registration routine that is called by Directory Server at startup. The registration routine explicitly registers each plug-in function with Directory Server.


Caution – Caution –

Plug-in libraries are linked with Directory Server. Plug-ins execute in the same memory address space as Directory Server. Plug-ins have direct access to data structures used by Directory Server. As a result, plug-ins can corrupt memory used by Directory Server. Such memory corruption can result in database corruption. Due to this danger, never use an untested plug-in on a production Directory Server.

Furthermore, Directory Server runs as a multi threaded process. Code you write for a plug-in must be reentrant. Multiple threads can call your entry point simultaneously. These threads can be rescheduled at any time.


Example Uses

To select the right plug-in type for the job is an art, rather than a science. The following table suggests example uses for documented plug-in types.

Table 1–2 Plug-In Example Uses by Type

Plug-In Type 

Example Uses 

Entry store-fetch 

Encoding and decoding entire plug-in entries 

Auditing or logging each entry as the entry is written to disk 

Extended operation 

Adding client services that are not available in LDAP v3 such as digital signatures in requests and responses 

Internal postoperation 

Auditing results of internal operations initiated by another plug-in 

Internal preoperation 

Preempting internal operations initiated by another plug-in 

Matching rule 

Offering enhanced sounds-like matching for directory searches 

Object 

Developing a plug-in that registers a group of other plug-ins with Directory Server 

Password check 

Forcing new passwords to conform to corporate policy for password syntax 

Password storage scheme 

Using a custom algorithm for password encryption instead of one of the algorithms supported by the standard product 

Postoperation 

Associating alerts and alarms sent after particular operations 

Auditing changes to specific entries 

Performing cleanup after an operation 

Preoperation 

Handling custom authentication methods external to the directory 

Forcing syntax checking for attribute values before adding or modifying an entry 

Adding attributes to or deleting attributes from a request 

Preprocessing client request content to translate requests from legacy applications 

Approving or rejecting the content of a client modification request before processing the request 

The list of example uses is by no means exhaustive, but is instead intended to help you brainstorm solutions.

Where to Go From Here

Most of the rest of this guide is devoted to examples that demonstrate the specific plug-in types.


Caution – Caution –

Never develop plug-ins on a production server, but instead on a test server used specifically for plug-in development.


Prepare Your Development Environment

You might not have installed Sun Java System Directory Server software. You might not have installed development software for the C language for use during plug-in development. Install the software now. Without development tools and a functioning Directory Server, you cannot use the examples discussed here.

Learn About Plug-In Development

If you have not yet written a plug-in for the current release, refer to Chapter 4, Getting Started With Directory Server Plug-Ins. The chapter helps you to build a simple plug-in, and to register the plug-in with Directory Server.

Upgrade Existing Plug-Ins

If you maintain plug-ins developed for a previous release of Directory Server, refer to Chapter 2, Changes to the Plug-In API Since Directory Server 5.2 and Chapter 3, Changes to the Plug-In API From Directory Server 4 to Directory Server 5.2 for information about what has changed.

Try a Sample Plug-In

Examples for several plug-in types are provided with the product in install-path/examples Subsequent chapters demonstrate the use of the sample plug-ins.

Find Details

See Part II, Directory Server Plug-In API Reference for details about particular data structures, functions, and parameter block semantics.

Chapter 2 Changes to the Plug-In API Since Directory Server 5.2

This chapter describes what's new in the Directory Server 6.2 release, that is, the changes to the plug-in API since the Directory Server 5.2 release. If you maintain Directory Server plug-ins originally developed for a previous release, consider upgrade such plug-ins to use the new and updated features.

For reference information, see Part II, Directory Server Plug-In API Reference.


Tip –

Consider working with Sun Services consultants to develop and to maintain your Directory Server plug-ins.


This chapter covers the following topics:

Deprecated and Changed Features Since Directory Server 5.2

This section addresses features deprecated or changed since the Directory Server5.2 release. Where possible, use the replacement functionality.

Attribute Value Handling Changes

The following table shows deprecated functions and their replacements for handling attribute values.

Table 2–1 Replacement Functions for Handling Attribute Values

Deprecated Function 

Replacement Function 

slapi_attr_first_value()

slapi_attr_first_value_const()

slapi_attr_next_value()

slapi_attr_next_value_const()

slapi_valueset_add_value()

slapi_valueset_add_value_optimised()

slapi_valueset_first_value()

slapi_valueset_first_value_const()

slapi_valueset_next_value()

slapi_valueset_next_value_const()

slapi_valueset_set_valueset()

slapi_valueset_set_valueset_optimised()

slapi_valueset_find()

slapi_valueset_find_const()

const Correct Code Changes

The following functions now apply const correctness:

The following callback data types also now apply const correctness:

For updated prototypes, see install-path/ds6/include/slapi-plugin.h.

New Features Since Directory Server 5.2

This section summarizes features that have been added since the Directory Server 5.2 release. This section does not include features that are reserved for internal use.

New Distinguished Name Functions

The following functions have been added to handle distinguished names (DNs):

slapi_dn_is_besuffix_norm()
slapi_sdn_get_suffix()

New Entry Handling Function

The slapi_entry_isroot() function has been added to handle entries.

New Modification Handling Function

The slapi_mods_remove_at() function has been added to handle modifications.

New Plug-In Call Ordering Mechanism

Directory Server now includes a mechanism for setting the order in which plug-ins are called by the server. See Ordering Plug-In Calls for details.

New Schema Checking Function

The slapi_entry_schema_check_ext() function has been added to handle schema checking.

New Suffix Functions

The following functions have been added to browse supported suffixes:

slapi_free_suffix_list()
slapi_get_suffix_list()

New Syntax Checking Functions

The following functions have been added to handle syntax checking:

slapi_entry_syntax_check()
slapi_ldapmods_syntax_check()
slapi_rdn_syntax_check()

New Virtual Attributes Function

The slapi_vattr_is_virtual() function has been added to handle virtual attributes.

+

Chapter 3 Changes to the Plug-In API From Directory Server 4 to Directory Server 5.2

This chapter describes the changes to the plug-in API from the Directory Server 4 release to and including the Directory Server 5.2 release. If you maintain Directory Server plug-ins originally developed for such releases or for a previous release, upgrade the plug-ins to use the new and updated features.

For reference information, see Part II, Directory Server Plug-In API Reference.


Tip –

You can use Sun Services consultants to develop and to maintain your Directory Server plug-ins.


This chapter covers the following topics:

Deprecated and Changed Features From 4 to 5.2

This section addresses features deprecated or changed since Directory Server 4 was released up to and including Directory Server 5.2. Where possible, use the replacement functionality.

For information about a function, see Part II, Directory Server Plug-In API Reference.

Handling Deprecation (4 to 5.2)

SLAPI_DEPRECATED has been defined in slapi-plugin.h to highlight what features have been superseded. If you cannot change core plug-in code, you might decide to recompile after including slapi-plugin-compat4.h to access deprecated features in this release.

#include "slapi-plugin-compat4.h"

In general, you must at minimum recompile your plug-ins so they work with Directory Server 5. Where possible, replace deprecated code with new alternatives before recompiling and testing.

Plug-In Directive Changes (4 to 5.2)

Plug-in directives have been deprecated. Use configuration entries instead. Refer to Plugging Libraries Into Directory Server for details on using configuration entries to load and configure plug-ins.

Function Parameter Changes (4 to 5.2)

Many of the existing function parameters have become const to reflect that parameter values are not changed when reading information.

Data Type Changes (4 to 5.2)

Consider using opaque data types wherever possible. For information about data types, see Chapter 15, Data Type and Structure Reference.

Plug-In Type Changes (4 to 5.2)

The SLAPI_PLUGIN_DATABASE plug-in type (database) has been deprecated.

Access Control Changes (4 to 5.2)

slapi_acl_verify_aci_syntax() now takes a pointer to a Slapi_PBlock as its first parameter. You must modify plug-in code to account for this change.

Attribute Handling Changes (4 to 5.2)

The following table shows deprecated functions and their replacements for handling attributes.

Table 3–1 Replacement Functions for Handling Attributes

Deprecated Function 

Replacement Function 

slapi_attr_get_values()

slapi_attr_get_bervals_copy()

slapi_attr_get_oid()

slapi_attr_get_oid_copy()

slapi_compute_add_search_rewriter()

slapi_compute_add_search_rewriter_ex()

Control Handling Changes (4 to 5.2)

slapi_get_supported_controls() is not thread-safe. The function has been deprecated. Use slapi_get_supported_controls_copy() instead.

SLAPI_OPERATION_* identifiers have changed to unsigned long values.

Entry Handling Changes (4 to 5.2)

The following table shows deprecated functions and their replacements for handling entries.

Table 3–2 Replacement Functions for Handling Entries

Deprecated Function 

Replacement Function 

slapi_entry_add_values()

slapi_entry_add_values_sv()

slapi_entry_add_valueset()

slapi_entry_attr_hasvalue()

slapi_entry_attr_has_syntax_value()

slapi_entry_attr_merge()

slapi_entry_attr_merge_sv()

slapi_entry_merge_values_sv()

slapi_entry_attr_replace()

slapi_entry_attr_replace_sv()

slapi_entry_delete_values()

slapi_entry_delete_values_sv()

Error Handling Changes (4 to 5.2)

slapi-plugin.h now includes ldap_msg.h, which lists unique error numbers used by Directory Server.

Functions for dealing with back ends that return int error codes can now also return SLAPI_FAIL_RETRY. The old values are also still used.

Directory Server now distinguishes between two results. SLAPI_BIND_FAIL indicates that the bind failed and the server prepared a result to send. SLAPI_BIND_ANONYMOUS indicates that the bind succeeded as an anonymous bind.

Extended Operations Function Changes (4 to 5.2)

slapi_get_supported_extended_ops() is not thread-safe. The function has been deprecated. Use slapi_get_supported_extended_ops_copy() instead.

Filter Handling Changes (4 to 5.2)

slapi_filter_get_type() has been deprecated. Use slapi_filter_get_attribute_type() instead. The replacement function works for all simple filter choices.

Internal Operation Changes (4 to 5.2)

The following table shows deprecated functions and their replacements for handling internal operations.

Table 3–3 Replacement Functions for Handling Internal Operations

Deprecated Function  

Replacement Function  

slapi_add_entry_internal()

slapi_add_internal_pb()

slapi_add_internal()

slapi_add_internal_pb()

slapi_delete_internal()

slapi_delete_internal_pb()

slapi_modify_internal()

slapi_modify_internal_pb()

slapi_modrdn_internal()

slapi_modrdn_internal_pb()

slapi_search_internal()

slapi_search_internal_pb()

slapi_search_internal_callback()

slapi_search_internal_callback_pb()

Logging Function Changes (4 to 5.2)

The following table shows deprecated functions and their replacements for logging.

Table 3–4 Replacement Functions for Logging

Deprecated Function  

Replacement Function  

slapi_log_error()

slapi_log_error_ex()

slapi_log_info_ex()

slapi_log_warning_ex()

slapi_is_loglevel_set()

No replacement is available 

Refer to Logging Plug-In Messages and all example code delivered with the product for demonstrations of how to use the new logging interface.

Parameter Block Argument Changes (4 to 5.2)

The following table shows deprecated parameter block arguments and their replacement arguments.

Table 3–5 Replacement Parameter Block Arguments

Deprecated Argument 

Replacement Argument 

SLAPI_CHANGENUMBER

No replacement is available 

SLAPI_CONN_AUTHTYPE

SLAPI_CONN_AUTHMETHOD

SLAPI_CONN_CLIENTIP

SLAPI_CONN_CLIENTNETADDR

SLAPI_CONN_SERVERIP

SLAPI_CONN_SERVERNETADDR

SLAPI_LOG_OPERATION

No replacement is available 

SLAPI_PLUGIN_DB_ABANDON_FN

SLAPI_PLUGIN_DB_ABORT_FN

SLAPI_PLUGIN_DB_ADD_FN

SLAPI_PLUGIN_DB_ARCHIVE2DB_FN

SLAPI_PLUGIN_DB_BEGIN_FN

SLAPI_PLUGIN_DB_BIND_FN

SLAPI_PLUGIN_DB_COMMIT_FN

SLAPI_PLUGIN_DB_COMPARE_FN

SLAPI_PLUGIN_DB_CONFIG_FN

SLAPI_PLUGIN_DB_DB2ARCHIVE_FN

SLAPI_PLUGIN_DB_DB2INDEX_FN

SLAPI_PLUGIN_DB_DB2LDIF_FN

SLAPI_PLUGIN_DB_DELETE_FN

SLAPI_PLUGIN_DB_ENTRY_FN

SLAPI_PLUGIN_DB_ENTRY_RELEASE_FN

SLAPI_PLUGIN_DB_FLUSH_FN

SLAPI_PLUGIN_DB_FREE_RESULT_SET_FN

SLAPI_PLUGIN_DB_LDIF2DB_FN

SLAPI_PLUGIN_DB_MODIFY_FN

SLAPI_PLUGIN_DB_MODRDN_FN

SLAPI_PLUGIN_DB_NEXT_SEARCH_ENTRY_EXT_FN

SLAPI_PLUGIN_DB_NEXT_SEARCH_ENTRY_FN

SLAPI_PLUGIN_DB_NO_ACL

SLAPI_PLUGIN_DB_REFERRAL_FN

SLAPI_PLUGIN_DB_RESULT_FN

SLAPI_PLUGIN_DB_SEARCH_FN

SLAPI_PLUGIN_DB_SEQ_FN

SLAPI_PLUGIN_DB_SIZE_FN

SLAPI_PLUGIN_DB_TEST_FN

SLAPI_PLUGIN_DB_UNBIND_FN

No replacement, as the database plug-in type is deprecated

SLAPI_REQUESTOR_ISUPDATEDN

No replacement is available 

Password Handling Changes (4 to 5.2)

slapi_pw_find() has been deprecated. Use slapi_pw_find_valueset() instead.

SASL Bind Changes (4 to 5.2)

slapi_get_supported_saslmechanisms() has been deprecated. Use slapi_get_supported_saslmechanisms_copy() instead.

New Features From Directory Server 4 to 5.2

This section summarizes features that were added since the Directory Server 4 was released up to and including Directory Server 5. This section does not include features that are reserved for internal use.

For information about a function, see Chapter 16, Function Reference, Part I.

New Plug-In API Version 3 (4 to 5.2)

The header file slapi-plugin.h identifies the current plug-in API as version 3. Plug-ins supporting API version 3 indicate the version in their description with SLAPI_PLUGIN_VERSION_03 or currently SLAPI_PLUGIN_CURRENT_VERSION.

New Plug-In Types (4 to 5.2)

New plug-in types have been added since the version 4 releases. For a list of supported plug-in types, refer to How Plug-Ins Interact With the Server.

New Plug-In Configuration Entries (4 to 5.2)

Configuration entries rather than directives are now used to configure Directory Server plug-ins. Refer to Plugging Libraries Into Directory Server for details on using configuration entries to load and configure plug-ins.

New NSPR 4 API

The Netscape Portable Runtime (NSPR) API allows compliant applications to use system facilities. The system facilities include threads, thread synchronization, I/O, interval timing, atomic operations, and other low-level services. NSPR allows access to these facilities in a platform-independent manner.

For information about the version of NSPR used currently, refer to http://www.mozilla.org/projects/nspr/release-notes/.

New Attribute Handling Functions and Flags (4 to 5.2)

The following table shows new flags defined for use with existing functions for handling attributes.

Table 3–6 New Flags for Handling Attributes

Function 

Flags Added 

slapi_attr_get_flags()

SLAPI_ATTR_FLAG_COLLECTIVE

SLAPI_ATTR_FLAG_NOUSERMOD

SLAPI_ATTR_FLAG_OBSOLETE

SLAPI_ATTR_FLAG_STD_ATTR

slapi_attr_type_cmp()

SLAPI_TYPE_CMP_BASE

SLAPI_TYPE_CMP_EXACT

SLAPI_TYPE_CMP_SUBTYPE

The old flags are still available. Refer to comments in slapi-plugin.h for hints on how to use the flags.

The following functions have been added to handle attributes and their values:

slapi_attr_add_value()
slapi_attr_first_value()
slapi_attr_get_numvalues()
slapi_attr_get_valueset()
slapi_attr_init()
slapi_attr_next_value()
slapi_attr_set_valueset()
slapi_attr_syntax_normalize()
slapi_attr_types_equivalent()

         

New Backend Handling Functions (4 to 5.2)

The following functions have been added to handle Slapi_Backend structures:

slapi_be_exist()
slapi_be_get_name()
slapi_be_get_readonly()
slapi_be_getsuffix()
slapi_be_gettype()
slapi_be_is_flag_set()
slapi_be_issuffix()
slapi_be_logchanges()
slapi_be_private()
slapi_be_select()
slapi_be_select_by_instance_name()
slapi_free_suffix_list()
slapi_get_first_backend()
slapi_get_next_backend()
slapi_get_suffix_list()
slapi_is_root_suffix()

New Control Handling Functions (4 to 5.2)

slapi_build_control()
slapi_build_control_from_berval()
slapi_dup_control()

New Opaque Data Structures (4 to 5.2)

The following data structures are now defined in slapi-plugin.h to wrap key objects used by Directory Server plug-ins:

Slapi_Attr
Slapi_Backend
Slapi_ComponentId
Slapi_Connection
Slapi_DN
Slapi_Entry
Slapi_Filter
Slapi_MatchingRuleEntry
Slapi_Mod
Slapi_Mods
Slapi_Operation
Slapi_PBlock
Slapi_RDN
Slapi_Value
Slapi_ValueSet

New Distinguished Name Handling Functions (4 to 5.2)

The following functions have been added to handle Slapi_DN structures:

slapi_dn_normalize_to_end()
slapi_dn_plus_rdn()
slapi_moddn_get_newdn()
slapi_sdn_compare()
slapi_sdn_copy()
slapi_sdn_done()
slapi_sdn_dup()
slapi_sdn_free()
slapi_sdn_get_backend_parent()
slapi_sdn_get_dn()
slapi_sdn_get_ndn()
slapi_sdn_get_ndn_len()
slapi_sdn_get_parent()
slapi_sdn_get_rdn()
slapi_sdn_is_rdn_component()
slapi_sdn_isempty()
slapi_sdn_isgrandparent()
slapi_sdn_isparent()
slapi_sdn_issuffix()
slapi_sdn_new()
slapi_sdn_new_dn_byref()
slapi_sdn_new_dn_byval()
slapi_sdn_new_dn_passin()
slapi_sdn_new_ndn_byref()
slapi_sdn_new_ndn_byval()
slapi_sdn_scope_test()
slapi_sdn_set_dn_byref()
slapi_sdn_set_dn_byval()
slapi_sdn_set_dn_passin()
slapi_sdn_set_ndn_byref()
slapi_sdn_set_ndn_byval()
slapi_sdn_set_parent()
slapi_sdn_set_rdn()

New Entry Handling Functions and Flags (4 to 5.2)

The slapi_str2entry() function contains the following new flags defined for handling entries:

SLAPI_STR2ENTRY_EXPAND_OBJECTCLASSES
SLAPI_STR2ENTRY_IGNORE_STATE
SLAPI_STR2ENTRY_INCLUDE_VERSION_STR
SLAPI_STR2ENTRY_NOT_WELL_FORMED_LDIF
SLAPI_STR2ENTRY_TOMBSTONE_CHECK

The old flags are still available. Refer to comments in slapi-plugin.h for hints on how to use the flags.

The following functions have been added to handle entries:

slapi_entry2str_with_options()
slapi_entry_add_string()
slapi_entry_add_value()
slapi_entry_attr_add()
slapi_entry_attr_get_long()
slapi_entry_attr_get_uint()
slapi_entry_attr_get_ulong()
slapi_entry_attr_remove()
slapi_entry_attr_set_int()
slapi_entry_attr_set_uint()
slapi_entry_attr_set_ulong()
slapi_entry_delete_string()
slapi_entry_get_dn_const()
slapi_entry_get_ndn()
slapi_entry_get_sdn()
slapi_entry_get_sdn_const()
slapi_entry_get_uniqueid()
slapi_entry_has_children()
slapi_entry_init()
slapi_entry_set_sdn()
slapi_entry_size()
slapi_is_rootdse()

New Filter Handling Functions (4 to 5.2)

The following functions have been added to handle Slapi_Filter structures:

slapi_filter_apply()
slapi_filter_compare()
slapi_filter_get_attribute_type()
slapi_filter_has_extension()
slapi_filter_test_simple()
slapi_find_matching_paren()
slapi_vattr_filter_test()

New Internal Operation Functions (4 to 5.2)

The following new functions are intended for use with the updated interface:

slapi_add_entry_internal_set_pb()
slapi_add_internal_set_pb()
slapi_delete_internal_set_pb()
slapi_modify_internal_set_pb()
slapi_rename_internal_set_pb()
slapi_search_internal_get_entry()
slapi_search_internal_set_pb()

Refer to Chapter 1, Before You Start Writing Plug-Ins to see how to use the updated interface.

New Memory Management Functions (4 to 5.2)

The following table shows functions that have been added to manage memory.

Table 3–7 New Functions for Managing Memory

Data Structure  

Memory Management Functions  

char *

slapi_ch_array_free()
slapi_ch_free_string()                        

Slapi_Attr

slapi_attr_dup()

slapi_attr_free()

slapi_attr_new()

Slapi_DN

slapi_sdn_dup()
slapi_sdn_free()
slapi_sdn_new()
slapi_sdn_new_dn_byref()
slapi_sdn_new_dn_byval()
slapi_sdn_new_dn_passin()
slapi_sdn_new_ndn_byref()
slapi_sdn_new_ndn_byval()                        

Slapi_Filter

slapi_filter_free()

Slapi_MatchingRuleEntry

slapi_matchingrule_free()

slapi_matchingrule_new()

Slapi_Mod

slapi_mod_free()

slapi_mod_new()

Slapi_Mods

slapi_mods_free()

slapi_mods_new()

Slapi_PBlock

slapi_pblock_destroy()

slapi_pblock_new()

Slapi_RDN

slapi_rdn_free()

slapi_rdn_new()

slapi_rdn_new_dn()

slapi_rdn_new_rdn()

slapi_rdn_new_sdn()

Slapi_Value

slapi_valuearray_free()

slapi_value_dup()

slapi_value_free()

slapi_value_new()

slapi_value_new_berval()

slapi_value_new_string()

slapi_value_new_string_passin()

slapi_value_new_value()

Slapi_ValueSet

slapi_valueset_free()

slapi_valueset_new()

New Modification Structure Functions (4 to 5.2)

The following functions have been added to handle Slapi_Mod structures:

slapi_mod_add_value()
slapi_mod_done()
slapi_mod_dump()
slapi_mod_free()
slapi_mod_get_first_value()
slapi_mod_get_next_value()
slapi_mod_get_num_values()
slapi_mod_get_operation()
slapi_mod_get_type()
slapi_mod_init()
slapi_mod_init_byref()
slapi_mod_init_byval()
slapi_mod_init_passin()
slapi_mod_isvalid()
slapi_mod_new()
slapi_mod_remove_value()
slapi_mod_set_operation()
slapi_mod_set_type()

The following functions have been added to handle Slapi_Mods structures:

slapi_entry2mods()
slapi_mods2entry()
slapi_mods_add()
slapi_mods_add_ldapmod()
slapi_mods_add_mod_values()
slapi_mods_add_modbvps()
slapi_mods_add_smod()
slapi_mods_add_string()
slapi_mods_done()
slapi_mods_dump()
slapi_mods_free()
slapi_mods_get_first_smod()
slapi_mods_get_next_mod()
slapi_mods_get_next_smod()
slapi_mods_get_num_mods()
slapi_mods_init()
slapi_mods_init_byref()
slapi_mods_init_passin()
slapi_mods_insert_after()
slapi_mods_insert_at()
slapi_mods_insert_before()
slapi_mods_insert_smod_at()
slapi_mods_insert_smod_before()
slapi_mods_iterator_backone()
slapi_mods_new()
slapi_mods_remove()

New Object Extensions (4 to 5.2)

Object extensions offer a new way of passing data through Directory Server from plug-in to plug-in. The following macros define extensible objects:

SLAPI_EXT_CONNECTION
SLAPI_EXT_ENTRY
SLAPI_EXT_MTNODE
SLAPI_EXT_OPERATION

The following new functions and associated constructor and destructor callbacks are part of the object extension interface:

slapi_extension_constructor_fnptr
slapi_extension_destructor_fnptr
slapi_get_object_extension()
slapi_register_object_extension()
slapi_set_object_extension()

New Operation Handling Functions (4 to 5.2)

slapi_op_get_type()

slapi_op_is_flag_set()

New Parameter Block Arguments (4 to 5.2)

SLAPI_CLIENT_DNS
SLAPI_CONFIG_DIRECTORY
SLAPI_CONN_CERT
SLAPI_CONN_CLIENTNETADDR
SLAPI_CONN_IS_REPLICATION_SESSION
SLAPI_CONN_IS_SSL_SESSION
SLAPI_CONN_SERVERNETADDR
SLAPI_CONTROLS_ARG
SLAPI_DESTROY_CONTENT
SLAPI_IS_INTERNAL_OPERATION
SLAPI_IS_REPLICATED_OPERATION
SLAPI_ORIGINAL_TARGET
SLAPI_ORIGINAL_TARGET_DN
SLAPI_PLUGIN_ENTRY_FETCH_FUNC
SLAPI_PLUGIN_ENTRY_STORE_FUNC
SLAPI_PLUGIN_IDENTITY
SLAPI_PLUGIN_INTERNAL_POST_ADD_FN
SLAPI_PLUGIN_INTERNAL_POST_DELETE_FN
SLAPI_PLUGIN_INTERNAL_POST_MODIFY_FN
SLAPI_PLUGIN_INTERNAL_POST_MODRDN_FN
SLAPI_PLUGIN_INTERNAL_PRE_ADD_FN
SLAPI_PLUGIN_INTERNAL_PRE_DELETE_FN
SLAPI_PLUGIN_INTERNAL_PRE_MODIFY_FN
SLAPI_PLUGIN_INTERNAL_PRE_MODRDN_FN
SLAPI_PLUGIN_POSTSTART_FN
SLAPI_PLUGIN_PWD_STORAGE_SCHEME_CMP_FN
SLAPI_PLUGIN_PWD_STORAGE_SCHEME_DB_PWD
SLAPI_PLUGIN_PWD_STORAGE_SCHEME_DEC_FN
SLAPI_PLUGIN_PWD_STORAGE_SCHEME_ENC_FN
SLAPI_PLUGIN_PWD_STORAGE_SCHEME_NAME
SLAPI_PLUGIN_PWD_STORAGE_SCHEME_USER_PWD
SLAPI_RESULT_CODE
SLAPI_RESULT_MATCHED
SLAPI_RESULT_TEXT

New Relative Distinguished Name Handling Functions (4 to 5.2)

The following functions have been added to handle Slapi_RDN structures:

slapi_rdn_add()
slapi_rdn_compare()
slapi_rdn_contains()
slapi_rdn_contains_attr()
slapi_rdn_done()
slapi_rdn_free()
slapi_rdn_get_first()
slapi_rdn_get_index()
slapi_rdn_get_index_attr()
slapi_rdn_get_next()
slapi_rdn_get_nrdn()
slapi_rdn_get_num_components()
slapi_rdn_get_rdn()
slapi_rdn_init()
slapi_rdn_init_dn()
slapi_rdn_init_rdn()
slapi_rdn_init_sdn()
slapi_rdn_isempty()
slapi_rdn_new()
slapi_rdn_new_dn()
slapi_rdn_new_rdn()
slapi_rdn_new_sdn()
slapi_rdn_remove()
slapi_rdn_remove_attr()
slapi_rdn_remove_index()
slapi_rdn_set_dn()
slapi_rdn_set_rdn()
slapi_rdn_set_sdn()
slapi_sdn_add_rdn()

New UTF8 Encoding Functions (4 to 5.2)

The following functions have been added to handle UTF8 encoding and decoding:

slapi_has8thBit()
slapi_UTF8CASECMP()
slapi_UTF8ISLOWER()
slapi_UTF8ISUPPER()
slapi_UTF8NCASECMP()
slapi_UTF8STRTOLOWER()
slapi_UTF8STRTOUPPER()
slapi_UTF8TOLOWER()
slapi_UTF8TOUPPER()

New Value Handling Functions (4 to 5.2)

The following functions have been added to handle Slapi_Value structures:

slapi_value_compare()
slapi_value_dup()
slapi_value_free()
slapi_value_get_berval()
slapi_value_get_int()
slapi_value_get_length()
slapi_value_get_long()
slapi_value_get_string()
slapi_value_get_uint()
slapi_value_get_ulong()
slapi_value_init()
slapi_value_init_berval()
slapi_value_init_string()
slapi_value_init_string_passin()
slapi_value_new()
slapi_value_new_berval()
slapi_value_new_string()
slapi_value_new_string_passin()
slapi_value_new_value()
slapi_value_set()
slapi_value_set_berval()
slapi_value_set_int()
slapi_value_set_string()
slapi_value_set_string_passin()
slapi_value_set_value()
slapi_valuearray_free()

New Value Set Handling Functions (4 to 5.2)

The following functions have been added to handle Slapi_Valueset structures:

slapi_valueset_add_value()
slapi_valueset_count()
slapi_valueset_done()
slapi_valueset_find()
slapi_valueset_first_value()
slapi_valueset_free()
slapi_valueset_init()
slapi_valueset_new()
slapi_valueset_next_value()
slapi_valueset_set_from_smod()
slapi_valueset_set_valueset()

New Virtual Attribute Functions (4 to 5.2)

slapi_entry_vattr_find()
slapi_vattr_attr_free()
slapi_vattr_attrs_free()
slapi_vattr_is_registered()
slapi_vattr_list_attrs()
slapi_vattr_values_free()
slapi_vattr_values_get_ex()

Chapter 4 Getting Started With Directory Server Plug-Ins

This chapter provides an introduction to creating Directory Server plug-ins including a minimal but complete plug-in example. This chapter also explains how to enable the server to use plug-ins and how to generate log entries.


Tip –

You can find complete code examples, including all code covered in this chapter, install-path/examples.


If you maintain plug-ins developed for a previous release of Directory Server, refer to Chapter 2, Changes to the Plug-In API Since Directory Server 5.2 and Chapter 3, Changes to the Plug-In API From Directory Server 4 to Directory Server 5.2 for information about what has changed.

This chapter covers the following topics:

A Hello World Plug-In

This section demonstrates a plug-in that logs a famous greeting. You can follow the process outlined in this section to understand the concepts that are covered in this chapter.

Find the Code

On the directory host, look in the install-path/examples/ directory. The example code that is covered in this section is hello.c.

Review the Plug-In

The following example logs Hello, World! at Directory Server startup.


Example 4–1 Hello, World! Plug-In (hello.c)

#include "slapi-plugin.h"

Slapi_PluginDesc desc = { 
    "Hello, World",                    /* plug-in identifier      */
    "Sun Microsystems, Inc.",          /* vendor name             */
    "6.0",                             /* plug-in revision number */
    "My first plug-in"                 /* plug-in description     */
};

/* Log a greeting at server startup if info logging is on for plug-ins */
int
hello()
{
    slapi_log_info_ex(
        SLAPI_LOG_INFO_AREA_PLUGIN,    /* Log if info logging is  */
        SLAPI_LOG_INFO_LEVEL_DEFAULT,  /* set for plug-ins.       */
        SLAPI_LOG_NO_MSGID,            /* No client at startup    */
        SLAPI_LOG_NO_CONNID,           /* No conn.  at startup    */
        SLAPI_LOG_NO_OPID,             /* No op.    at startup    */
        "hello() in My first plug-in", /* Origin of this message  */
        "Hello, World!\n"              /* Informational message   */
    );
    return 0;
}

/* Register the plug-in with the server */
#ifdef _WIN32
__declspec(dllexport)
#endif
int
hello_init(Slapi_PBlock * pb)
{
    int rc = 0;                        /* 0 means success         */
    rc |= slapi_pblock_set(            /* Plug-in API version     */
        pb,
        SLAPI_PLUGIN_VERSION,
        SLAPI_PLUGIN_CURRENT_VERSION
    );
    rc |= slapi_pblock_set(            /* Plug-in description     */
        pb,
        SLAPI_PLUGIN_DESCRIPTION,
        (void *) &desc;
    );
    rc |= slapi_pblock_set(            /* Startup function        */
        pb,
        SLAPI_PLUGIN_START_FN,
        (void *) hello
    );
    return rc;
}

To log the greeting, the plug-in includes a function to be called at Directory Server startup. The plug-in also includes an initialization function to register the plug-in description, supported API version, and startup function with Directory Server.

The startup function specifies that the message is from a plug-in, SLAPI_LOG_INFO_AREA_PLUGIN. The startup function also specifies that the message should be logged when informational logging is activated, SLAPI_LOG_INFO_LEVEL_DEFAULT. For this log message, no client connection information is available, SLAPI_LOG_NO_MSGID, SLAPI_LOG_NO_CONNID, SLAPI_LOG_NO_OPID. Directory Server calls the function at startup before any clients have connected. The function specifies where the log message originates, "hello() in My first plug-in". Finally, the function provides the famous log message.

The initialization function is named hello_init(). This function modifies the parameter block, pb, with the function slapi_pblock_set(). The function thus registers the plug-in API version supported, the plug-in description, and the functions offered to Directory Server by this plug-in. As required for all plug-in initialization functions, hello_init() returns 0 on success, -1 on failure. The function slapi_pblock_set() returns 0 if successful, -1 otherwise. Therefore, you do not need to set the return code to -1 explicitly in Example 4–1.

Build the Plug-In

Build the plug-in as a shared object, libtest-plugin.so or libtest-plugin.sl, or dynamic link library, testplugin.dll, depending on your platform. On SolarisTM SPARC systems, you do the following.


$ gmake -f Makefile

On Solaris x64 systems where you boot a 64–bit kernel, you do the following.


$ gmake -f Makefile64

Use the Makefile or Makefile64 in install-path/examples/ to compile and to link the code. This file builds and links all plug-in examples into the same library. Refer to Searching Plug-In Libraries for details when building a plug-in for use with a 64-bit Directory Server instance.

Plug In the Plug-In

Use the dsconf(1M) command to add information about the plug-in to the server configuration.

Updating Directory Server Configuration

Configure the server to log plug-in messages:


$ dsconf set-log-prop -h localhost -p 1389 error level:err-plugins

Configure the server to load the plug-in:


$ dsconf create-plugin -h localhost -p 1389 \
 -H lib-path -F hello_init -Y object "Hello World"
$ dsconf set-plugin-prop "Hello World" feature:"Hello, World" version:6.0 
 vendor:"Sun Microsystems, Inc." desc:"My first plug-in"
$ dsconf enable-plugin -h localhost -p 1389 "Hello World"
Directory Server needs to be restarted for changes to take effect

Here, lib-path must correspond to the absolute path to the plug-in library, such as /local/myplugins/examples/libtest-plugin.so. Directory Server requires an absolute path, not a relative path. Before setting lib-path on a 64–bit system, read Searching Plug-In Libraries.


Note –

Plug-ins delivered with Directory Server have signatures, which are stored as ds-pluginSignature attribute values. Plug-ins also have digests, which are stored as ds-pluginDigest attribute values. The values allow you to differentiate plug-ins that are delivered with Directory Server from custom plug-ins.


Restarting Directory Server

After changing the Directory Server configuration, you must restart Directory Server for the server to register the plug-in. Use the dsadm(1M) command.

For example, to restart a server instance in /local/ds/, type the following:

$ dsadm restart /local/ds
Waiting for server to stop...
Server stopped
Server started: pid=4362

Checking the Log

After Directory Server has started, search the error log, instance-path/logs/errors, for Hello, World! You should find an entry similar to the following line, which is wrapped for the printed page:

[02/Jan/2006:16:56:07 +0100] - INFORMATION - 
 hello() in My first plug-in - conn=-1 op=-1 msgId=-1 -  Hello, World!

At this point, reset the log level to the default to avoid logging unnecessary messages:


$ dsconf set-log-prop -h localhost -p 1389 error level:default

Writing Directory Server Plug-Ins

This section focuses on the basics of coding a Directory Server plug-in. This section covers the key tasks. These tasks include specifying the header file, writing an initialization function, setting parameter block values, and registering plug-in functions.

Include the slapi-plugin.h Header File

The plug-in API is defined in install-path/include/slapi-plugin.h. Observe that the header file includes ldap.h, the entry point for the standard and extended LDAP C APIs, and ldap_msg.h, the list of error message identifiers used by Directory Server.

In general, interfaces that Directory Server exposes are specified in install-path/include/. For details about specific features of the API, refer to Part II, Directory Server Plug-In API Reference.

To use the API, include slapi-plugin.h in the declaration section of your plug-in source:

#include "slapi-plugin.h"

As a rule, use appropriate macros in your Makefile or project file to tell the linker to look for header files in install-path/include/.

Write Your Plug-In Functions

Directory Server calls plug-in functions in the context of a Directory Server operation, for example when a bind, add, search, modify, or delete is performed. Do not export these functions. The functions become available in the appropriate scope when registered with Directory Server at startup. This guide covers how to write such functions.

A plug-in function prototype looks like the prototype of any other locally used function. Many plug-in functions are passed a parameter block.

int prebind_auth(Slapi_PBlock * pb); /* External authentication. */

You can also use additional helper functions that are not registered with Directory Server. This guide does not cover additional helper functions, but some of the sample plug-ins delivered with the product include such functions.

Use Appropriate Return Codes

In general, return 0 from your plug-in functions when they complete successfully. For some functions that search for matches, 0 means a successful match, and -1 means that no match is found. For preoperation functions, 0 means that Directory Server should continue processing the operation. When you want the server to stop processing the operation after your preoperation function completes, return a positive value such as 1.

When plug-in functions do not complete successfully, send a result to the client if appropriate, log an error message, and return a non-zero value, such as the LDAP result code from install-path/include/ldap-standard.h, for example.

Write an Initialization Function

All plug-ins must include an initialization function. The initialization function registers the plug-in version compatibility, the plug-in description, the plug-in functions, and any other data required by the plug-in. The initialization function takes a pointer to a Slapi_PBlock structure as a parameter. Refer to Example 4–1.

The initialization function returns 0 if everything registers successfully. The initialization function returns -1 if registration fails for any configuration information or function. A return code of -1 from a plug-in initialization function prevents the server from starting.

Directory Server can call the initialization function more than once during startup.

Set Configuration Information Through the Parameter Block

Directory Server passes a parameter block pointer to the plug-in initialization function. This parameter block holds configuration information. The parameter block can hold other data from Directory Server. The parameter block is the structure into which you add configuration information and pointers to plug-in functions using calls to slapi_pblock_set().


Tip –

To read parameter values, use slapi_pblock_get(). To write parameter values, use slapi_pblock_set(). Using other functions can cause the server to crash.


Specifying Compatibility

You specify compatibility with the plug-in API version as defined in slapi-plugin.h, which is described in Part II, Directory Server Plug-In API Reference. If you are creating a new plug-in, for example, use SLAPI_PLUGIN_CURRENT_VERSION or SLAPI_PLUGIN_VERSION_03. For example, to specify that a plug-in supports the current version, version 3, of the plug-in API, use the following:

slapi_pblock_set(pb,SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_03);

Here, pb is the parameter block pointer passed to the initialization function. SLAPI_PLUGIN_VERSION signifies that you are setting plug-in API version compatibility in the parameter block.

Specifying the Plug-In Description

Specify the plug-in description in a Slapi_PluginDesc structure, as specified in slapi-plugin.h and described in Part II, Directory Server Plug-In API Reference. Call slapi_pblock_set() to register the description:

slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION,(void *) &desc);

Here, desc is a Slapi_PluginDesc structure that contains the plug-in description. See Example 4–1.

Set Pointers to Functions Through the Parameter Block

Register plug-in functions according to what operation Directory Server performs. Also register plug-in functions according to when the operation is performed. Directory Server typically calls plug-in functions before, during, or after a standard operation.

Macros that specify when Directory Server should call plug-ins have the form SLAPI_PLUGIN_*_FN. As the sample plug-ins demonstrate, the macro used to register a plug-in function depends entirely on what the function is supposed to do. For example, to register a plug-in function foo() to be called at Directory Server startup, do the following:

slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN, (void *) foo);

Here, pb is the parameter block pointer passed to the initialization function. SLAPI_PLUGIN_START_FN signifies that Directory Server calls foo() at startup. Note that foo() returns 0 on success.

Locate Examples

Sample plug-ins are located in install-path/examples/.

Building Directory Server Plug-Ins

This section explains how to build plug-in binaries. This section deals with compilation requirements and with linking requirements for Directory Server plug-ins.

Include the Header File for the Plug-In API

The compiler needs to be able to locate the header files such as slapi-plugin.h. Find header files in install-path/include/.

Link the Plug-In as a Shared Object or Dynamic Link Library

Link plug-ins so the plug-in library is reentrant and portable and can be shared. The following example shows one way of building a plug-in library. The example works with the Sun compiler that is used with a 64-bit Directory Server running on Solaris platforms.


Example 4–2 64-bit: Makefile for a Solaris Sample Plug-In Library

INCLUDE_FLAGS = -I../include
CFLAGS = $(INCLUDE_FLAGS) -D_REENTRANT -KPIC -xarch=v9 -DUSE_64
LDFLAGS = -G
DIR64 = 64

OBJS = dns.o entries.o hello.o internal.o testpwdstore.o \
 testsaslbind.o testextendedop.o testpreop.o testpostop.o \
 testentry.o testbind.o testgetip.o

all: MKDIR64 $(DIR64)/libtest-plugin.so

MKDIR64:
    @if [ ! -d $(DIR64) ]; then mkdir $(DIR64); fi

$(DIR64)/libtest-plugin.so: $(OBJS)
    $(LD) $(LDFLAGS) -o $@ $(OBJS)

.c.o:
    $(CC) $(CFLAGS) -c $<

clean:
    -rm -f $(OBJS) libtest-plugin.so $(DIR64)/libtest-plugin.so

The CFLAGS -xarch=v9 and -DUSE_64 specify that the compiler builds the library for use with a 64-bit server. Notice also that the 64-bit library is placed in a directory that is named 64/. This extra directory is required for 64-bit plug-ins. A 64-bit server adds the name of the extra directory to the path name when searching for the library. Refer to Searching Plug-In Libraries for further information.

The following example shows a 32-bit equivalent of the Makefile.


Example 4–3 32-bit: Makefile for a Solaris Sample Plug-In Library

INCLUDE_FLAGS = -I../include
CFLAGS = $(INCLUDE_FLAGS) -D_REENTRANT -KPIC
LDFLAGS = -G

OBJS = dns.o entries.o hello.o internal.o testpwdstore.o \
 testsaslbind.o testextendedop.o testpreop.o testpostop.o \
 testentry.o testbind.o testgetip.o

all: libtest-plugin.so

libtest-plugin.so: $(OBJS)
    $(LD) $(LDFLAGS) -o $@ $(OBJS)

.c.o:
    $(CC) $(CFLAGS) -c $<

clean:
    -rm -f $(OBJS) libtest-plugin.so

Notice that both 32-bit versions and 64-bit versions of the plug-in library can coexist on the same host.

Locate the Example Build Rules

Build rules are in install-path/examples/. The rules are also in install-path/examples/Makefile64. Refer to these files for the recommended setup for compiling and linking on your platform.

Plugging Libraries Into Directory Server

This section covers server plug-in configuration. This section also explains how plug-ins are loaded. When plug-ins are properly loaded, Directory Server can initialize and register the plug-ins correctly. Dependencies between Directory Server plug-ins are also discussed.

Specify Plug-In Configuration Settings

Plug-in configuration settings specify information that Directory Server requires. The server uses the information to load a plug-in at startup, to identify the plug-in, and to pass parameters to the plug-in at load time.

You access configuration settings through the dsconf(1M) command, using the subcommands create-plugin, set-plugin-prop, and enable-plugin.

The dsconf create-plugin command forces you to set required plug-in configuration settings. The settings include the name, plug-in initialization function, path to the library that contains the plug-in, and plug-in type.

The dsconf set-plugin-prop command allows you to set optional configuration settings.

The dsconf enable-plugin command allows you to turn the plug-in on or off. When you change this setting, you must restart the server.

The following table identifies the configuration settings and indicates whether the settings are required, set with dsconf create-plugin, or optional, set with dsconf set-plugin-prop.

Table 4–1 Plug-In Configuration Settings

Setting 

Description 

Required or Optional 

Plug-in name 

Common name for the plug-in, corresponding to the name registered by the plug-in 

Required 

Initialization function (-F)

Name of the initialization function that is called during Directory Server startup as the name appears in the plug-in code 

Required 

Library path (-H)

Full path to the library that contains the plug-in, not including 64-bit directory for 64-bit plug-in libraries 

Refer to Searching Plug-In Libraries for details.

Required 

Plug-in type (-Y)

Plug-in type that defines the types of functions the plug-in implements 

Refer to Understanding Plug-In Types and Dependencies for details.

Required 

argument

A parameter passed to the plug-in at Directory Server startup such as suffix=dc=example,dc=com

Refer to Retrieving Arguments Passed to Plug-Ins

Optional 

depends-on-named

One or more plug-in dependencies on plug-in names (RDN) such as State Change Plugin or Multimaster Replication Plugin

Refer to Understanding Plug-In Types and Dependencies for details.

Optional 

depends-on-type

One or more plug-in dependencies on plug-in types such as preoperation or database

Refer to Understanding Plug-In Types and Dependencies for details.

Optional 

desc

One-line description as specified in the spd_description field of the plug-in Slapi_PluginDesc structure

Optional 

feature

Identifier as specified in the spd_id field of the plug-in Slapi_PluginDesc structure

Optional 

vendor

Vendor name as specified in the spd_vendor field of the plug-in Slapi_PluginDesc structure

Optional 

version

Version number as specified in the spd_version field of the plug-in Slapi_PluginDesc structure

Optional 

Understanding Plug-In Types and Dependencies

The plug-in type tells Directory Server which type of plug-in functions can be registered by a plug-in. A plug-in type corresponds to the principal type of operation that the plug-in performs. Refer to How Plug-Ins Interact With the Server and Example Uses for explanations of plug-in types. Part II, Directory Server Plug-In API Reference describes plug-in types and lists type property settings that correspond to the type identifiers specified in slapi-plugin.h.

Determining the plug-in type is clear-cut when you know what the plug-in must do. For example, if you write a plug-in to handle authentication of a request before Directory Server processes the bind for that request, the type is preoperation. In other words, the plug-in does something to the request prior to the bind operation. Such a plug-in therefore registers at least a pre-bind plug-in function to process the request prior to the bind. If, on the other hand, you write a plug-in to encode passwords, and also compare incoming passwords with encoded passwords, the type is pwdstoragescheme. Such a plug-in registers at least password encoding, and also compares functions. In both cases, plug-in type follows plug-in function.

Plug-in dependencies tell Directory Server that a plug-in requires one or more other plug-ins in order to function properly. Directory Server resolves dependencies as the server loads the plug-ins. Therefore, Directory Server fails to register a plug-in if a plug-in that the plug-in depends on cannot be registered.

Specify plug-in dependencies by name, using depends-on-named, or by type, using depends-on-type. Here, plug-in name refers to the plug-in relative distinguished name (RDN) from the configuration entry. Plug-in type is a type identifier from the list of types in Chapter 18, Parameter Block Reference. Both depends-on-named and nsslapddepends-on-type can take multiple values.

A plug-in configuration entry can specify either kind or both kinds of dependencies. Use the configuration entry to identify dependencies on specific plug-ins by name. Identify dependencies on a type of plug-in by type. If a dependency identifies a plug-in by name, Directory Server only registers the plug-in after the server registers the named plug-in. If a dependency identifies a plug-in by type, Directory Server only registers the plug-in after the server registers all plug-ins of the specified type.

Do not confuse plug-in registration dependencies with a mechanism to specify the order in which Directory Server calls plug-ins.

Ordering Plug-In Calls

Directory Server allows you to define the order in which plug-ins are called. This mechanism is independent of the mechanism that defines plug-in load dependencies.

This mechanism uses a set of attributes on the entry with DN cn=plugins,cn=monitor. Plug-in call ordering is defined for each type of plug-in on attributes whose values are comma-separated lists of plug-in names. Each attribute type name starts with plugin-list- and identifies a point in the server request processing where plug-ins can be called. In the comma-separated list of plug-ins, the first plug-in in the list is called first, the second is called second, and so forth.

For example, six postoperation plug-ins are called after a modify operation. The plugin-list-postoperation-modify attribute on cn=plugins,cn=monitor shows the order in which these six plug-ins are called by the server.

plugin-list-postoperation-modify:
 Class of Service,
 Legacy replication postoperation plugin,
 Multimaster replication postoperation plugin,
 Retrocl postoperation plugin,
 Roles Plugin,
 State Change Plugin

You can also retrieve these attributes by performing a search on the entry with DN cn=plugins,cn=monitor.

To change the order in which plug-ins are called, you must not modify the value of the attribute type starting with plugin-list-. Instead, you must modify the value of a corresponding attribute type starting with plugin-order-, and having the same ending as the plugin-list- attribute.

The full list of plug-in call ordering attribute types is as follows:

For example, if you want to change the order in which postoperation plug-ins are called after a modify, first read plugin-list-postoperation-modify. Next set plugin-order-postoperation-modify, copying plug-in names from the value of plugin-list-postoperation-modify. Then restart Directory Server. Plug-in names are not case—sensitive, but white spaces are taken into account. Unrecognized names are ignored.

With a single asterisk, *, as an item in the comma-separated list, you let Directory Server define the call order for plug-ins you do not identify explicitly.

If you have two postoperation modify plug-ins, the first, Call Me First, and the second, Call Me Last, you could set plugin-list-postoperation-modify as follows:

plugin-order-postoperation-modify: Call Me First,*,Call Me Last

The plug-in name that you specify must be identical to the name argument that is passed to the slapi_register_plugin function. There must also be no white space between the comma delimiter and the adjacent plug-in names, and no white space at the end of the names list.

Plug-ins can remain that you do not explicitly identify by name or implicitly identify using *. Directory Server calls such plug-ins before calling the plug-ins that you listed.

Retrieving Arguments Passed to Plug-Ins

Your plug-in can retrieve parameters specified in the configuration settings property argument, which is multivalued. Directory Server makes the argument available to the plug-in through the parameter block passed to the plug-in initialization function.

If you include parameters in the configuration settings, the following apply:

The following example uses slapi_pblock_get() to retrieve the arguments from install-path/examples/testextendedop.c.


Example 4–4 Plug–In Using Arguments (testextendedop.c)

#include "slapi-plugin.h"

Slapi_PluginDesc expdesc = {
    "test-extendedop",                 /* plug-in identifier       */
    "Sun Microsystems, Inc.",          /* vendor name              */
    "6.0",                             /* plug-in revision number  */
    "Sample extended operation plug-in"/* plug-in description      */
};

/* Register the plug-in with the server.                           */
#ifdef _WIN32
__declspec(dllexport)
#endif
int
testexop_init(Slapi_PBlock * pb)
{
    char ** argv;                      /* Args from configuration  */
    int     argc;                      /* entry for plug-in.       */
    char ** oid_list;                  /* OIDs supported           */
    int     rc = 0;                    /* 0 means success          */
    int     i;

    /* Get the arguments from the configuration entry.             */
    rc |= slapi_pblock_get(pb, SLAPI_PLUGIN_ARGV, &argv);
    rc |= slapi_pblock_get(pb, SLAPI_PLUGIN_ARGC, &argc);
    if (rc != 0) {
        slapi_log_info_ex(
            SLAPI_LOG_INFO_AREA_PLUGIN,
            SLAPI_LOG_INFO_LEVEL_DEFAULT,
            SLAPI_LOG_NO_MSGID,
            SLAPI_LOG_NO_CONNID,
            SLAPI_LOG_NO_OPID,
            "testexop_init in test-extendedop plug-in",
            "Could not get plug-in arguments.\n"
        );
        return (rc);
    }

    /* Extended operation plug-ins may handle a range of OIDs. */
    oid_list = (char **)slapi_ch_malloc((argc + 1) * sizeof(char *));
    for (i = 0; i < argc; ++i) {
        oid_list[i] = slapi_ch_strdup(argv[i]);
        slapi_log_info_ex(
            SLAPI_LOG_INFO_AREA_PLUGIN,
            SLAPI_LOG_INFO_LEVEL_DEFAULT,
            SLAPI_LOG_NO_MSGID,
            SLAPI_LOG_NO_CONNID,
            SLAPI_LOG_NO_OPID,
            "testexop_init in test-extendedop plug-in",
            "Registering plug-in for extended operation %s.\n",
            oid_list[i]
        );
    }
    oid_list[argc] = NULL;
    
    rc |= slapi_pblock_set(            /* Plug-in API version      */
        pb,
        SLAPI_PLUGIN_VERSION,
        SLAPI_PLUGIN_CURRENT_VERSION
    );
    rc |= slapi_pblock_set(            /* Plug-in description      */
        pb,
        SLAPI_PLUGIN_DESCRIPTION,
        (void *) &expdesc
    );
    rc |= slapi_pblock_set(            /* Extended op. handler     */
        pb,
        SLAPI_PLUGIN_EXT_OP_FN,
        (void *) test_extendedop
    );
    rc |= slapi_pblock_set(            /* List of OIDs handled     */
        pb,
        SLAPI_PLUGIN_EXT_OP_OIDLIST,
        oid_list
    );
    return (rc);
}

You can specify multiple arguments by using multiple properties in the command line. The dsconf command causes the server to provide the arguments to the plug-in in the order you specify on the command line. When you change the list of arguments by using the dsconf command, the new arguments replace the existing arguments. The arguments are not appended to the list.

Searching Plug-In Libraries

Directory Server searches for plug-in libraries with the library path that you specify with the -H option to the dsconf create-plugin command.

Directory Server fails to start when the server cannot find a plug-in library specified in the configuration entry.

Modify the Directory Server Configuration

With Directory Server running, add your plug-in configuration settings with the dsconf(1M) command. Enable the plug-in with the dsconf enable-plugin name command.

If everything works as expected, Directory Server adds a plug-in configuration entry to the Directory Server configuration, which is stored in instance-path/config/dse.ldif. Do not modify the configuration file directly.

Restart Directory Server

Directory Server does not load plug-ins dynamically. Instead, you must restart the server. Directory Server then registers your plug-in at startup.

Set logging so Directory Server logs plug-in informational messages with the dsconf set-log-prop error level:err-plugins command. After setting the log level appropriately, restart the server to ensure that it loads the new plug-ins that you configured:

$ dsadm restart instance-path

You can adjust log levels while the server is running.

Logging Plug-In Messages

This sectioon shows how to do the following:

Log Three Levels of Message Severity

The plug-in API provides log functions for logging fatal error messages, warnings, and information. Error messages and warnings are always logged. Informational logging is turned off by default. You can adjust log levels while Directory Server is running.


Tip –

Directory Server, as Directory Server waits for the system to write every log message to disk, slowing the server down.

Use logging when you need logging. Turn logging off when you do not need to use it.


Refer to Chapter 16, Function Reference, Part I for details about each of the logging functions.

Fatal Error Messages

For fatal errors, use slapi_log_error_ex(). In many cases, you might return -1 after you log the message to indicate to Directory Server that a serious problem has occurred.


Example 4–5 Logging a Fatal Error Message

#include "slapi-plugin.h"
#include "example-com-error-ids.h" /* example.com unique
                                      error IDs file       */
int
foobar(Slapi_PBlock * pb)
{
    char * error_cause;
    int    apocalypse = 1;         /* Expect the worst.    */

    /* ... */

    if (apocalypse) {              /* Server to crash soon */
        slapi_log_error_ex(
            EXCOM_SERVER_MORIBUND, /* Unique error ID      */
            SLAPI_LOG_NO_MSGID,
            SLAPI_LOG_NO_CONNID,
            SLAPI_LOG_NO_OPID,
            "example.com: foobar in baz plug-in",
            "cannot write to file system: %s\n",
            error_cause
        );
        return -1;
    }
    return 0;
}

In this example, foobar() logs an error as Directory Server is about to crash.


Tip –

If the plug-ins are internationalized, use macros, not literal strings, for the last two arguments to slapi_log_*_ex() functions.


Warning Messages

For serious situations that require attention and in which messages should always be logged, use slapi_log_warning_ex().

In the following example, foobar() logs a warning because the disk is nearly full.


Example 4–6 Logging a Warning Message

#include "slapi-plugin.h"
#include "example-com-warning-ids.h" /* example.com unique
                                        warning IDs file  */
int
foobar()
{
    int disk_use_percentage;

    /* ... */

    if (disk_use_percentage >= 95) {
        slapi_log_warning_ex(
            EXCOM_DISK_FULL_WARN,    /* unique warning ID */
            SLAPI_LOG_NO_MSGID,
            SLAPI_LOG_NO_CONNID,
            SLAPI_LOG_NO_OPID,
            "example.com: foobar in baz plug-in",
            "disk %.0f%% full, find more space\n",
            disk_use_percentage
        );
    }
    return 0;
}

Informational Messages

For informational or debug messages, use slapi_log_info_ex(). This function can be set for default logging, which means that the message is not logged when logging is turned off for plug-ins. Informational logging can also be set to occur only when heavy logging is used. Thus, the message is logged when plug-in logging is both turned on and set to log all informational messages.

Refer to Review the Plug-In for an example of how to log an informational message from a plug-in.

Set the Appropriate Log Level in the Directory Server Configuration

Log levels can be set for plug-in informational messages, as well as for a number of other Directory Server subsystems. You can set the log level through Directory Service Control Center. You can also set the log level from the command line by using the dsconf set-log-prop command.

When you set dsconf set-log-prop error level:err-plugins, the server turns on plug-in informational logging.

Find Messages in the Log

The log file for errors, warnings, and informational messages is instance-path/logs/errors. All messages go to the same log so you can easily determine the order in which events occurred.

As shown in this example, messages consist of a timestamp, followed by an identifier, followed by the other information in the log message. Note that this log message is broken for the printed page.


[02/Jan/2006:16:54:57 +0100] - INFORMATION - 
 start_tls - conn=-1 op=-1 msgId=-1 - 
 Start TLS extended operation request confirmed.

Use the identifiers to filter what you do not need.

Chapter 5 Working With Entries Using Plug-Ins

This chapter covers plug-in API features for handling directory entries, attributes, attribute values, and distinguished names (DNs). This chapter also deals with converting entries to and from LDAP Data Interchange Format (LDIF) strings, and checking whether entries comply with LDAP schema.

Code excerpts used in this chapter can be found in the install-path/examples/entries.c sample plug-in and the install-path/examples/dns.c sample plug-in.

See Chapter 16, Function Reference, Part I for information about the plug-in API functions used in this chapter.

This chapter covers the following topics:

Creating Entries

This section demonstrates how to create an entry in the directory. Create a new entry by allocating space, and creating a completely new entry. Alternatively, create a new entry by duplicating an existing entry and modifying its values.

Creating New Entries

Create a completely new entry by using slapi_entry_alloc() to allocate the memory that is required, as shown in the following example.


Example 5–1 Creating a New Entry (entries.c)

#include "slapi-plugin.h"

int
test_ldif()
{
    Slapi_Entry * entry = NULL;        /* Entry to hold LDIF      */

    /* Allocate the Slapi_Entry structure.                        */
    entry = slapi_entry_alloc();

    /* Add code that fills the Slapi_Entry structure.             */

    /* Add code that uses the Slapi_Entry structure.              */

    /* Release memory allocated for the entry.                    */
    slapi_entry_free(entry);

    return (0);
}

Creating Copies of Entries

Create a copy of an entry with slapi_entry_dup().

Converting To and From LDIF Representations

This section shows how to use the plug-in API to convert entries that are represented in LDIF to Slapi_Entry structures. This section also shows how to convert Slapi_Entry structures to LDIF strings.

LDIF files appear as a series of human-readable representations of directory entries, and optionally access control instructions. Entries that are represented in LDIF start with a line for the distinguished name. The LDIF representation continues with optional lines for the attributes. The syntax is shown in the following example.


Example 5–2 LDIF Syntax Representing an Entry

dn:[:] dn-value\n
[attribute:[:] value\n]
[attribute:[:] value\n]
[single-space continued-value\n]*

A double colon, ::, indicates that the value is base64-encoded. Base64-encoded values can, for example, have a line break in the midst of an attribute value.

As shown in the preceding example, LDIF lines can be folded by leaving a single space at the beginning of the continued line.

Sample LDIF files can be found in install-path/ldif/.

Refer to Chapter 13, Directory Server LDIF and Search Filters, in Sun Java System Directory Server Enterprise Edition 6.2 Reference for details on LDIF syntax.

Converting an LDIF String to a Slapi_Entry Structure

You can achieve this type of conversion by using slapi_str2entry(). The function takes as its two arguments the string to convert and an int holding flag of the form SLAPI_STR2ENTRY_* in slapi-plugin.h. The function returns a pointer to a Slapi_Entry if successful, NULL otherwise, as shown in the following example.


Example 5–3 Converting To and From LDIF Strings (entries.c)

#include "slapi-plugin.h"

#define LDIF_STR "dn: dc=example,dc=com\nobjectclass: \
    top\nobjectclass: domain\ndc: example\n"

int
test_ldif()
{
    char        * ldif  = NULL;        /* Example LDIF string     */
    Slapi_Entry * entry = NULL;        /* Entry to hold LDIF      */
    char        * str   = NULL;        /* String to hold entry    */
    int           len;                 /* Length of entry as LDIF */

    /* LDIF to Slapi_Entry                                        */
    entry = slapi_entry_alloc();
    ldif  = slapi_ch_strdup(LDIF_STR);
    entry = slapi_str2entry(ldif, SLAPI_STR2ENTRY_ADDRDNVALS);
    slapi_ch_free_string(&ldif);
    if (entry == NULL) return (-1);

    /* Slapi_Entry to LDIF                                        */
    str = slapi_entry2str(entry, &len);
    if (str == NULL) return (-1);
    slapi_log_info_ex(
        SLAPI_LOG_INFO_AREA_PLUGIN,
        SLAPI_LOG_INFO_LEVEL_DEFAULT,
        SLAPI_LOG_NO_MSGID,
        SLAPI_LOG_NO_CONNID,
        SLAPI_LOG_NO_OPID,
        "test_ldif in test-entries plug-in",
        "\nOriginal entry:\n%sEntry length: %d\n", str, len
    );

    slapi_entry_free(entry);

    return (0);
}

Here, SLAPI_STR2ENTRY_ADDRDNVALS adds any missing relative distinguished name (RDN) values, as specified in slapi-plugin.h where supported flags for slapi_str2entry() are listed.

Converting a Slapi_Entry Structure to an LDIF String

You can achieve this type of conversion by using slapi_entry2str(). This function takes as its two arguments the entry to convert and an int to hold the length of the string that is returned. The function returns a char * to the LDIF if successful, NULL otherwise, as shown in Example 38–3.

Getting Entry Attributes and Attribute Values

This section demonstrates how to iterate through real attributes of an entry. You can use this technique when looking through the list of attributes that belong to an entry. If you know that the entry contains a particular attribute, slapi_entry_attr_find() returns a pointer to the attribute directly.


Note –

Real attributes contrast with virtual attributes generated by Directory Server for advanced entry management with roles and Class of Service (CoS).

When working with virtual attributes such as attributes for CoS, you can use slapi_vattr_list_attrs(). This function provides the complete list of real attributes as well as virtual attributes that belong to an entry.


Iteration through attribute values can be done using slapi_attr_first_value_const() and slapi_attr_next_value_const() together as shown in the following example.


Example 5–4 Iterating Through Attributes of an Entry (testpreop.c)

#include "slapi-plugin.h"

int
testpreop_add(Slapi_PBlock * pb)
{
    Slapi_Entry * entry;               /* Entry to add            */
    Slapi_Attr  * attribute;           /* Entry attributes        */
    Slapi_Value ** values;             /* Modified, duplicate vals*/
    int         connId, opId, rc = 0;
    long        msgId;

    /* Get the entry. */
    rc |= slapi_pblock_get(pb, SLAPI_ADD_ENTRY,       &entry);
    rc |= slapi_pblock_get(pb, SLAPI_OPERATION_MSGID, &msgId);
    rc |= slapi_pblock_get(pb, SLAPI_CONN_ID,         &connId);
    rc |= slapi_pblock_get(pb, SLAPI_OPERATION_ID,    &opId);

    /* Prepend ADD to values of description attribute.            */
    rc |= slapi_entry_attr_find(entry, "description", &attribute);
    if (rc == 0) {                 /* Found a description, so...  */
        int nvals, i, count = 0;
        const Slapi_Value * value;

        slapi_attr_get_numvalues(attribute, &nvals);
        values = (Slapi_Value **)
            slapi_ch_malloc((nvals+1)*sizeof(Slapi_Value *));

        for (                          /* ...loop for value...    */
            i = slapi_attr_first_value_const(attribute, &value);
            i != -1;
            i = slapi_attr_next_value_const(attribute, i, &value)
        ) {                            /* ...prepend "ADD ".      */
            const char  * string;
            char        * tmp;
            values[count] = slapi_value_dup(value);
            string = slapi_value_get_string(values[count]);
            tmp    = slapi_ch_malloc(5+strlen(string));
            strcpy(tmp, "ADD ");
            strcpy(tmp+4, string);
            slapi_value_set_string(values[count], tmp);
            slapi_ch_free((void **)&tmp);
            ++count;
        }

        values[count] = NULL;

    } else {                           /* entry has no desc.      */
        slapi_log_info_ex(
            SLAPI_LOG_INFO_AREA_PLUGIN,
            SLAPI_LOG_INFO_LEVEL_DEFAULT,
            msgId,
            connId,
            opId,
            "testpreop_add in test-preop plug-in",
            "Entry has no description attribute.\n"
        );
    }

    rc = slapi_entry_attr_replace_sv(entry, "description", values);
    if (rc != 0) {
        slapi_log_info_ex(
            SLAPI_LOG_INFO_AREA_PLUGIN,
            SLAPI_LOG_INFO_LEVEL_DEFAULT,
            msgId,
            connId,
            opId,
            "testpreop_add in test-preop plug-in",
            "Description attribute(s) not modified.\n"
        );
    }
    slapi_valuearray_free(&values);

    return 0;
}

Adding and Removing Attribute Values

This section demonstrates how to add and remove attributes and their values.

Adding Attribute Values

If the attribute name and value are strings, you might be able to use slapi_entry_add_string(). The following example builds an entry first by setting the DN, then by adding attributes with string values.


Example 5–5 Adding String Attribute Values (entries.c)

#include "slapi-plugin.h"

int
test_create_entry()
{
    Slapi_Entry * entry = NULL;        /* Original entry          */

    entry = slapi_entry_alloc();
    slapi_entry_set_dn(entry, slapi_ch_strdup("dc=example,dc=com"));
    slapi_entry_add_string(entry, "objectclass", "top");
    slapi_entry_add_string(entry, "objectclass", "domain");
    slapi_entry_add_string(entry, "dc", "example");

    /* Add code using the entry you created.....                  */

    slapi_entry_free(entry);

    return (0);
}

When working with values that already exist or with values that are not strings, you can use the following functions

The following example demonstrates slapi_entry_attr_merge_sv().


Example 5–6 Merging Attribute Values (entries.c)

#include "slapi-plugin.h"

int
test_ldif()
{
    Slapi_Entry * entry = NULL;        /* Entry to hold LDIF      */
    Slapi_Value * values[2];           /* Attribute values        */

    entry = slapi_entry_alloc();

    /* Add code to transform an LDIF representation into an entry.*/

    /* Add a description by setting the value of the attribute.
     * Although this is overkill when manipulating string values,
     * it can be handy when manipulating binary values.           */
    values[0] = slapi_value_new_string("Description for the entry.");
    values[1] = NULL;
    if (slapi_entry_attr_merge_sv(entry,"description",values) != 0)
        /* Merge did not work if processing arrives here. */ ;

    slapi_entry_free(entry);

    return (0);
}

If the attribute exists in the entry, slapi_entry_attr_merge_sv() merges the specified values into the set of existing values. If the attribute does not exist, slapi_entry_attr_merge_sv() adds the attribute with the specified values.

Removing Attribute Values

Remove attribute values with slapi_entry_delete_string() or slapi_entry_delete_values_sv().

Verifying Schema Compliance for an Entry

This section demonstrates how to check that an entry is valid with respect to the directory schema known to Directory Server. Verify schema compliance for an entry with slapi_entry_check(). The two arguments of the function are a pointer to a parameter block and a pointer to the entry, as shown in the following example.


Example 5–7 Checking Schema Compliance (entries.c)

#include "slapi-plugin.h"

int
test_create_entry()
{
    Slapi_Entry * entry = NULL;        /* Original entry          */
    Slapi_Entry * ecopy = NULL;        /* Copy entry              */

    entry = slapi_entry_alloc();

    /* Add code to fill the entry, setting the DN and attributes. */

    ecopy = slapi_entry_dup(entry);
    slapi_entry_set_dn(ecopy, slapi_ch_strdup("dc=example,dc=org"));
    slapi_entry_add_string(ecopy, "description", "A copy of the orig.");
    
    /* Does the resulting copy comply with the schema?            */
    if (slapi_entry_schema_check(NULL, ecopy) == 0) {
        /* Resulting entry does comply. */ ;
    } else {
        /* Resulting entry does not comply. */ ;
    }

    slapi_entry_free(entry);
    slapi_entry_free(ecopy);

    return (0);
}

Notice that the parameter block pointer argument is NULL. Leave the parameter block pointer argument NULL in most cases. When the plug-in is used in a replicated environment, you can use the argument to prevent schema compliance verification for replicated operations.

Handling Entry Distinguished Names

This section demonstrates how to work with distinguished names (DNs) to do the following:

Getting the Parent and Suffix DNs

Use the slapi_dn_parent() function to return the parent and slapi_dn_beparent() to return the suffix associated with the entry, as shown in the following example.


Example 5–8 Determining the Parent and Suffix of an Entry (dns.c)

#include "slapi-plugin.h"

int
test_dns(Slapi_PBlock * pb)
{
    char * bind_DN;                    /* DN being used to bind    */
    char * parent_DN;                  /* DN of parent entry       */
    char * suffix_DN;                  /* DN of suffix entry       */
    int    connId, opId, rc = 0;
    long   msgId;

    rc |= slapi_pblock_get(pb, SLAPI_BIND_TARGET,     &bind_DN);
    rc |= slapi_pblock_get(pb, SLAPI_OPERATION_MSGID, &msgId);
    rc |= slapi_pblock_get(pb, SLAPI_CONN_ID,         &connId);
    rc |= slapi_pblock_get(pb, SLAPI_OPERATION_ID,    &opId);
    if (rc != 0) return (-1);

    /* Get the parent DN of the DN being used to bind.             */
    parent_DN = slapi_dn_parent(bind_DN);
    slapi_log_info_ex(
        SLAPI_LOG_INFO_AREA_PLUGIN,
        SLAPI_LOG_INFO_LEVEL_DEFAULT,
        msgId,
        connId,
        opId,
        "test_dns in test-dns plug-in",
        "Parent DN of %s: %s\n", bind_DN, parent_DN
    );

    /* Get the suffix DN of the DN being used to bind.             */
    suffix_DN = slapi_dn_beparent(pb, bind_DN);
    if (suffix_DN != NULL) {
        slapi_log_info_ex(
            SLAPI_LOG_INFO_AREA_PLUGIN,
            SLAPI_LOG_INFO_LEVEL_DEFAULT,
            msgId,
            connId,
            opId,
            "test_dns in test-dns plug-in",
            "Suffix for user (%s) is (%s).\n", bind_DN, suffix_DN
        );
    }

    return rc;
}

Notice that the suffix and parent DNs can be the same if the tree is not complex. For example, if the bind_DN is uid=bjensen,ou=People,dc=example,dc=com, the parent is ou=People,dc=example,dc=com. In this case, the parent is the same as the suffix.

Determining Whether a Suffix Is Served Locally

Use slapi_dn_isbesuffix() function which takes as its two arguments a parameter block and a char * to the root suffix DN, as demonstrated in the following example.


Example 5–9 Determining Whether a Suffix Is Local (dns.c)

#include "slapi-plugin.h"

int
test_dns(Slapi_PBlock * pb)
{
    char * bind_DN;                    /* DN being used to bind    */
    char * parent_DN;                  /* DN of parent entry       */
    char * suffix_DN;                  /* DN of suffix entry       */

    slapi_pblock_get(pb, SLAPI_BIND_TARGET, &bind_DN);

    /* Get the suffix DN of the DN being used to bind.             */
    suffix_DN = slapi_dn_beparent(pb, bind_DN);

    /* Climb the tree to the top suffix and check if it is local.  */
    while (suffix_DN != NULL &&
        !slapi_dn_isbesuffix(pb, suffix_DN)) {
        suffix_DN = slapi_dn_parent(suffix_DN);
    }
    if (suffix_DN != NULL) {
        /* Suffix is served locally. */ ;
    } else {
        /* Suffix is not served locally. */ ;
    }

    return 0;
}

Notice the use of slapi_dn_isbesuffix() to move up the tree. Notice also that slapi_dn_parent() keeps working away at the DN until the DN that is left is NULL. The parent does not necessarily correspond to an actual entry.

Getting and Setting Entry DNs

To get and set entry DNs, use slapi_entry_get_dn() and slapi_entry_set_dn(), respectively. Verifying Schema Compliance for an Entry demonstrates how to use slapi_entry_set_dn() to change the DN of a copy of an entry. Notice the use of slapi_ch_strdup() to ensure that the old DN in the copy is not used.

Normalizing a DN

Before comparing DNs, you can normalize the DNs to prevent the comparison from failing due to extra white space or different capitalization. You can normalize a DN with slapi_dn_normalize() and slapi_dn_normalize_case(), as shown in the following example.


Example 5–10 Normalizing a DN (dns.c)

#include "slapi-plugin.h"

int
test_norm()
{
    char * test_DN;                    /* Original, not normalized */
    char * copy_DN;                    /* Copy that is normalized. */

    test_DN = "dc=Example,     dc=COM";/* Prior to normalization...*/

    /* When normalizing the DN with slapi_dn_normalize() and
     * slapi_dn_normalize_case(), the DN is changed in place.
     * Use slapi_ch_strdup() to work on a copy.                    */
    copy_DN = slapi_ch_strdup(test_DN);
    copy_DN = slapi_dn_normalize_case(copy_DN);

    return 0;
}

The function slapi_dn_normalize_case() works directly on the char * DN passed as an argument. This prepares the string for “case ignore matching” as specified, but not defined, for X.500.


Note –

Use slapi_ch_strdup() to make a copy first if you do not want to modify the original.


Is the User the Directory Manager?

Answer this question with slapi_dn_isroot(). The bind for the directory superuser, however, as for anonymous users, is handled as a special case. For such users, the server front end handles the bind before calling preoperation bind plug-ins.

Chapter 6 Extending Client Request Handling Using Plug-Ins

This chapter covers support in the plug-in API for modifying what Directory Server does before or after carrying out operations for clients.

Although related to the bind operation, authentication is described independently in Chapter 7, Handling Authentication Using Plug-Ins.

This chapter cover the following topics:

Preoperation and Postoperation Plug-Ins

When processing client requests and when sending information to clients, Directory Server calls preoperation and postoperation plug-in functions.

Preoperation Plug-Ins

Preoperation plug-ins are called before a client request is processed. Use preoperation plug-ins to validate attribute values, and to add to and delete from attributes that are provided in a request, for example.

Postoperation Plug-Ins

Postoperation plug-ins are called after a client request has been processed and all results have been sent to the client, whether or not the operation completes successfully. Use postoperation plug-ins to send alerts, or to send alarms. Also use postoperation plug-ins to perform auditing work, or to perform cleanup work.


Note –

Directory Server calls preoperation and postoperation plug-ins only when an external client is involved. Internally, Directory Server also performs operations that no external client has requested.

To make this possible, Directory Server distinguishes between external operations and internal operations. External operations respond to incoming client requests. Internal operations are actions that are performed without a corresponding client request.


Several operations support the use of preoperation and postoperation plug-in functions. Operations include abandon, add, bind, compare, delete, modify, modify RDN, search, send entry, send referral, send result, and unbind. The plug-ins function regardless of the Directory Server front end contacted by the client application.

As is the case for other plug-in functions, slapi_pblock_set() is used in the plug-in initialization function to register preoperation and postoperation plug-in functions. slapi_pblock_set() is described in Part II, Directory Server Plug-In API Reference.

Registration Identifiers

Preoperation plug-in registrations use IDs of the form SLAPI_PLUGIN_PRE_operation_FN. Postoperation registrations use IDs of the form SLAPI_PLUGIN_POST_operation_FN. Here, operation is one of ABANDON, ADD, BIND, COMPARE, DELETE, ENTRY (sending entries to the client), MODIFY, MODRDN, REFERRAL (sending referrals to the client), RESULT (sending results to the client), SEARCH, or UNBIND.


Note –

Preoperation and postoperation plug-ins can also register functions to run at Directory Server startup, using SLAPI_PLUGIN_START_FN, and at Directory Server shutdown, using SLAPI_PLUGIN_STOP_FN.


Part II, Directory Server Plug-In API Reference describes these IDs. Refer also to install-path/include/slapi-plugin.h for the complete list of IDs used at build time.

The following example demonstrates how the functions are registered with Directory Server by using the appropriate registration IDs.


Example 6–1 Registering Postoperation Functions (testpostop.c)

#include "slapi-plugin.h"

Slapi_PluginDesc postop_desc = {
    "test-postop",                     /* plug-in identifier         */
    "Sun Microsystems, Inc.",          /* vendor name                */
    "6.0",                             /* plug-in revision number    */
    "Sample post-operation plug-in"    /* plug-in description        */
};

static Slapi_ComponentId * postop_id;  /* Used to set log            */

/* Register the plug-in with the server. */
#ifdef _WIN32
__declspec(dllexport)
#endif
int
testpostop_init(Slapi_PBlock * pb)
{
    int rc = 0;                        /* 0 means success            */
    rc |= slapi_pblock_set(            /* Plug-in API version        */
        pb,
        SLAPI_PLUGIN_VERSION,
        SLAPI_PLUGIN_CURRENT_VERSION
    );
    rc |= slapi_pblock_set(            /* Plug-in description        */
        pb,
        SLAPI_PLUGIN_DESCRIPTION,
        (void *) &postop_desc
    );
    rc |= slapi_pblock_set(            /* Open log at startup        */
        pb,
        SLAPI_PLUGIN_START_FN,
        (void *) testpostop_set_log
    );
    rc |= slapi_pblock_set(            /* Post-op add function       */
        pb,
        SLAPI_PLUGIN_POST_ADD_FN,
        (void *) testpostop_add
    );
    rc |= slapi_pblock_set(            /* Post-op modify function    */
        pb,
        SLAPI_PLUGIN_POST_MODIFY_FN,
        (void *) testpostop_mod
    );
    rc |= slapi_pblock_set(            /* Post-op delete function    */
        pb,
        SLAPI_PLUGIN_POST_DELETE_FN,
        (void *) testpostop_del
    );
    rc |= slapi_pblock_set(            /* Post-op modrdn function    */
        pb,
        SLAPI_PLUGIN_POST_MODRDN_FN,
        (void *) testpostop_modrdn
    );
    rc |= slapi_pblock_set(            /* Close log on shutdown      */
        pb,
        SLAPI_PLUGIN_CLOSE_FN,
        (void *) testpostop_free_log
    );
    /* Plug-in identifier is required for internal search.           */
    rc |= slapi_pblock_get(pb, SLAPI_PLUGIN_IDENTITY, &postop_id);
    return (rc);
}

In addition to its postoperation functions, the plug-in that is shown in the preceding example registers a function. The plug-in opens a log file at startup. The plug-in closes the log file at shutdown. For details, refer to install-path/examples/testpostop.c.

Location of Plug-In Examples

Plug-in examples are located in install-path/examples/.

Extending the Bind Operation

This section shows how to develop functions called by Directory Server before client bind operations.


Note –

Pre-bind plug-in functions are often used to handle extensions to authentication. Yet, you might have to account for special cases such as binds by the directory superuser and anonymous users. Sometimes, you have to account for multiple calls to the same preoperation or postoperation plug-in function.


ProcedureTo Set Up an Example Suffix

If you have not done so already, set up a directory instance with a suffix, dc=example,dc=com, containing data loaded from a sample LDIF file, install-path/ds6/ldif/Example.ldif.

  1. Create a new Directory Server instance.

    For example:


    $ dsadm create /local/ds
    Choose the Directory Manager password:
    Confirm the Directory Manager password:
    $ 
  2. Start the new Directory Server instance.

    For example:


    $ dsadm start /local/ds
    Server started: pid=4705
    $ 
  3. Create a suffix called dc=example,dc=com.

    For example, with long lines folded for the printed page:


    $ dsconf create-suffix -h localhost -p 1389 dc=example,dc=com
    Enter "cn=directory manager" password: 
    Certificate "CN=defaultCert, CN=hostname:1636" presented by the
     server is not trusted.
    Type "Y" to accept, "y" to accept just once,
     "n" to refuse, "d" for more details: Y
    $ 
  4. Load the sample LDIF.

    For example, with long lines folded for the printed page:


    $ dsconf import -h localhost -p 1389 \
     /opt/SUNWdsee/ds6/ldif/Example.ldif dc=example,dc=com
    Enter "cn=directory manager" password:  
    New data will override existing data of the suffix
     "dc=example,dc=com".
    Initialization will have to be performed on replicated suffixes. 
    Do you want to continue [y/n] ? y
    
    ## Index buffering enabled with bucket size 16
    ## Beginning import job...
    ## Processing file "/opt/SUNWdsee/ds6/ldif/Example.ldif"
    ## Finished scanning file "/opt/SUNWdsee/ds6/ldif/Example.ldif" (160 entries)
    ## Workers finished; cleaning up...
    ## Workers cleaned up.
    ## Cleaning up producer thread...
    ## Indexing complete.
    ## Starting numsubordinates attribute generation.
     This may take a while, please wait for further activity reports.
    ## Numsubordinates attribute generation complete. Flushing caches...
    ## Closing files...
    ## Import complete. Processed 160 entries in 5 seconds.
     (32.00 entries/sec)
    
    Task completed (slapd exit code: 0).
    $ 
See Also

You can use Directory Service Control Center to perform this task. For more information, see the Directory Service Control Center online help.

Logging the Authentication Method

The following example logs the bind authentication method. Refer to install-path/examples/testpreop.c for complete example code.


Example 6–2 Logging the Authentication Method (testpreop.c)

#include "slapi-plugin.h"

int
testpreop_bind(Slapi_PBlock * pb)
{
    char * auth;                       /* Authentication type     */
    char * dn;                         /* Target DN               */
    int    method;                     /* Authentication method   */
    int    connId, opId, rc = 0;
    long   msgId;

    /* Get target DN for bind and authentication method used.     */
    rc |= slapi_pblock_get(pb, SLAPI_BIND_TARGET,     &dn);
    rc |= slapi_pblock_get(pb, SLAPI_BIND_METHOD,     &method);
    rc |= slapi_pblock_get(pb, SLAPI_OPERATION_MSGID, &msgId);
    rc |= slapi_pblock_get(pb, SLAPI_CONN_ID,         &connId);
    rc |= slapi_pblock_get(pb, SLAPI_OPERATION_ID,    &opId);
    if (rc == 0) {
        switch (method) {
            case LDAP_AUTH_NONE:   auth = "No authentication";
                break;
            case LDAP_AUTH_SIMPLE: auth = "Simple authentication";
                break;
            case LDAP_AUTH_SASL:   auth = "SASL authentication";
                break;
            default: auth = "Unknown authentication method";
                break;
        }
    } else {
        return (rc);
    }

    /* Log target DN and authentication method info.              */
    slapi_log_info_ex(
        SLAPI_LOG_INFO_AREA_PLUGIN,
        SLAPI_LOG_INFO_LEVEL_DEFAULT,
        msgId,
        connId,
        opId,
        "testpreop_bind in test-preop plug-in",
        "Target DN: %s\tAuthentication method: %s\n", dn, auth
    );
    return (rc);
}

This plug-in function sets the auth message based on the authentication method. The function does nothing to affect the way Directory Server processes the bind.

ProcedureTo Register the Plug-In

If you have not already done so, build the example plug-in library and activate both plug-in informational logging and the example plug-in.

  1. Build the plug-in.

    Hint Use install-path/examples/Makefile or install-path/examples/Makefile64.

  2. Configure Directory Server to log plug-in informational messages and load the plug-in.

    Hint Use the commands specified in the comments at the outset of the plug-in source file.

  3. Restart Directory Server.


    $ dsadm restart instance-path
    

ProcedureTo Generate a Bind Log Message

  1. Bind as Kirsten Vaughan (for example).


    $ ldapsearch -h localhost -p 1389 -b "dc=example,dc=com" \
     -D "uid=kvaughan,ou=people,dc=example,dc=com" -w bribery "(uid=*)"
  2. Search instance-path/logs/errors for the resulting message from the testpreop_bind() function.

    If you ignore housekeeping information for the entry, output similar to this appears:


    Target DN: uid=kvaughan,ou=people,dc=example,dc=com
    Authentication method: Simple authentication

    For a discussion of less trivial pre-bind plug-in functions, refer to Chapter 7, Handling Authentication Using Plug-Ins.

Bypassing Bind Processing in Directory Server

When the plug-in returns 0, Directory Server continues to process the bind. To bypass Directory Server bind processing, set SLAPI_CONN_DN in the parameter block, and return a positive value, such as 1.

Normal Directory Server Bind Behavior

Directory Server follows the LDAP bind model. At minimum, the server authenticates the client. The server also sends a bind response to indicate the status of authentication. Refer to RFC 1777, Lightweight Directory Access Protocol, and RFC 45111, Lightweight Directory Access Protocol (v3), for details.


Note –

Lightweight Directory Access Protocol (v3) is the preferred protocol because Lightweight Directory Access Protocol (v2) is obsolete.


Extending the Search Operation

This section shows how to develop functionality called by Directory Server before LDAP search operations.

Logging Who Requests a Search

The following example logs the DN of the client that requests the search. Refer to install-path/examples/testbind.c for complete example code.

Before using the plug-in function as shown in this section, set up the example suffix and register the plug-in. See Extending the Bind Operation and “To register the Plug-in”, as described previously. The plug-in, Test Bind, also includes the pre-search function.

The test_search() function logs the request, as shown in the following example.


Example 6–3 Getting the DN of the Client Requesting a Search (testbind.c)

#include "slapi-plugin.h"

int
test_search(Slapi_PBlock * pb)
{
    char * requestor_dn;               /* DN of client searching    */
    int    is_repl;                    /* Is this replication?      */
    int    is_intl;                    /* Is this an internal op?   */
    int    connId, opId, rc = 0;
    long   msgId;

    rc |=slapi_pblock_get(pb, SLAPI_OPERATION_MSGID,         &msgId);
    rc |=slapi_pblock_get(pb, SLAPI_CONN_ID,                 &connId);
    rc |=slapi_pblock_get(pb, SLAPI_OPERATION_ID,            &opId);
    rc |=slapi_pblock_get(pb, SLAPI_REQUESTOR_DN,            &requestor_dn);
    rc |=slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_repl);
    rc |=slapi_pblock_get(pb, SLAPI_IS_INTERNAL_OPERATION,   &is_intl);
    if (rc != 0) return (rc);

    /* Do not interfere with replication and internal operations.   */
    if (is_repl || is_intl) return 0;

    if (requestor_dn != NULL && *requestor_dn != '\0') {
        slapi_log_info_ex(
            SLAPI_LOG_INFO_AREA_PLUGIN,
            SLAPI_LOG_INFO_LEVEL_DEFAULT,
            msgId,
            connId,
            opId,
            "test_search in test-bind plug-in",
            "Search requested by %s\n", requestor_dn
        );
    } else {
        slapi_log_info_ex(
            SLAPI_LOG_INFO_AREA_PLUGIN,
            SLAPI_LOG_INFO_LEVEL_DEFAULT,
            msgId,
            connId,
            opId,
            "test_search in test-bind plug-in",
            "Search requested by anonymous client.\n"
        );
    }
    return (rc);
}

After activating the plug-in in the server, perform a search.


$ ldapsearch -D uid=kvaughan,ou=people,dc=example,dc=com -w bribery \
 -h localhost -p 1389 -b dc=example,dc=com uid=bjensen

Search instance-path/logs/errors for the resulting message. The last field of the log entry shows the following:

With the plug-in activated in Directory Server, perform a search as Kirsten Vaughan:

Authenticated: uid=kvaughan,ou=people,dc=example,dc=com

Breaking Down a Search Filter

Breaking Down a Search Filter breaks down a search filter, logging the component parts of the filter. For complete example code, refer to install-path/examples/testpreop.c.

LOG Macros for Compact Code

The code for the SLAPI_PLUGIN_PRE_SEARCH_FN function, testpreop_search(), is preceded by three macros. Search testpreop.c for #define LOG1(format). The purpose of these macros is only to render the subsequent logging statements more compact, making the code easier to decipher. The digits in LOG1, LOG2, and LOG3 reflect the number of parameters each macro takes. The actual number of parameters that you pass to the log functions varies, as the log functions let you format strings in the style of printf().

Parameter Block Contents

The parameter block passed to the pre-search function contains information about the target and scope of the search. The following example shows how to obtain this information. The scope, as specified in RFC 4511, Lightweight Directory Access Protocol (v3), can include one of the following:


Example 6–4 Logging Base and Scope for a Search (testpreop.c)

#include "slapi-plugin.h"

int
testpreop_search(Slapi_PBlock * pb)
{
    char          *  base       = NULL;/* Base DN for search      */
    int              scope;            /* Base, 1 level, subtree  */
    int              rc = 0;

    rc |= slapi_pblock_get(pb, SLAPI_SEARCH_TARGET,    &base);
    rc |= slapi_pblock_get(pb, SLAPI_SEARCH_SCOPE,     &scope);
    if (rc == 0) {
        switch (scope) {
        case LDAP_SCOPE_BASE:
            LOG2("Target DN: %s\tScope: LDAP_SCOPE_BASE\n", base);
            break;
        case LDAP_SCOPE_ONELEVEL:
            LOG2("Target DN: %s\tScope: LDAP_SCOPE_ONELEVEL\n", base);
            break;
        case LDAP_SCOPE_SUBTREE:
            LOG2("Target DN: %s\tScope: LDAP_SCOPE_SUBTREE\n", base);
            break;
        default:
            LOG3("Target DN: %s\tScope: unknown value %d\n", base, scope);
            break;
        }
    } /* Continue processing... */

    return 0;
}

In writing a plug-in mapping X.500 search targets to LDAP search targets, you could choose to parse and transform the base DN for searches.

The parameter block also contains the following information:

Search Filter Info

The search filter that you obtain from the parameter block must be considered. testpreop_search() gets the filter from the parameter block both as a Slapi_Filter structure, filter, and as a string, filter_str. Depending on what kind of search filter is passed to Directory Server and retrieved in the Slapi_Filter structure, the plug-in breaks the filter down in different ways. If the function were post-search rather than pre-search, you would likely want to access the results that are returned through the parameter block. See Part II, Directory Server Plug-In API Reference for instructions on accessing the results.

The plug-in API offers several functions for manipulating the search filter. testpreop_search() uses slapi_filter_get_choice() to determine the kind of filter used. The preoperation search function also uses slapi_filter_get_subfilt() to break down substrings that are used in the filter. The preoperation search function further uses slapi_filter_get_type() to find the attribute type when searching for the presence of an attribute. The preoperation search function uses slapi_filter_get_ava() to obtain attribute types and values used for comparisons.

The following example shows a code excerpt that retrieves information from the search filter.


Example 6–5 Retrieving Filter Information (testpreop.c)

#include "slapi-plugin.h"

int
testpreop_search(Slapi_PBlock * pb)
{
    char          *  attr_type  = NULL;/* For substr and compare  */
    char          *  substr_init= NULL;/* For substring filters   */
    char          *  substr_final=NULL;
    char          ** substr_any = NULL;
    int              i, rc =0;
    Slapi_Filter  *  filter;

    rc |= slapi_pblock_get(pb, SLAPI_SEARCH_FILTER,    &filter);
    if (rc == 0) {
        filter_type = slapi_filter_get_choice(filter);
        switch (filter_type) {
        case LDAP_FILTER_AND:
        case LDAP_FILTER_OR:
        case LDAP_FILTER_NOT:
            LOG1("Search filter: complex boolean\n");
            break;
        case LDAP_FILTER_EQUALITY:
            LOG1("Search filter: LDAP_FILTER_EQUALITY\n");
            break;
        case LDAP_FILTER_GE:
            LOG1("Search filter: LDAP_FILTER_GE\n");
            break;
        case LDAP_FILTER_LE:
            LOG1("Search filter: LDAP_FILTER_LE\n");
            break;
        case LDAP_FILTER_APPROX:
            LOG1("Search filter: LDAP_FILTER_APPROX\n");
            break;
        case LDAP_FILTER_SUBSTRINGS:
            LOG1("Search filter: LDAP_FILTER_SUBSTRINGS\n");
            slapi_filter_get_subfilt(
                filter,
                &attr_type,
                &substr_init,
                &substr_any,
                &substr_final
            );
            if (attr_type != NULL)
                LOG2("\tAttribute type: %s\n", attr_type);
            if (substr_init != NULL)
                LOG2("\tInitial substring: %s\n", substr_init);
            if (substr_any != NULL)
                for (i = 0; substr_any[i] != NULL; ++i) {
                    LOG3("\tSubstring# %d: %s\n", i, substr_any[i]);
                }
            if (substr_final != NULL)
                LOG2("\tFinal substring: %s\n", substr_final);
            break;
        case LDAP_FILTER_PRESENT:
            LOG1("Search filter: LDAP_FILTER_PRESENT\n");
            slapi_filter_get_attribute_type(filter, &attr_type);
            LOG2("\tAttribute type: %s\n", attr_type);
            break;
        case LDAP_FILTER_EXTENDED:
            LOG1("Search filter: LDAP_FILTER_EXTENDED\n");
            break;
        default:
            LOG2("Search filter: unknown type %d\n", filter_type);
            break;
        }
    }
    return (rc);
}

Before using the plug-in function as described here, set up the example suffix and register the plug-in. See Extending the Bind Operation and “To register the Plug-in”, as described previously.

After the example suffix and plug-in have been loaded into the directory, run a search to generate output in instance-path/logs/errors.


$ ldapsearch -h localhost -p 1389 -b "dc=example,dc=com" "(uid=*go*iv*)"

When you ignore the housekeeping information in the error log, the search filter breakdown occurss as shown in the following example.


Example 6–6 Search Filter Breakdown

*** PREOPERATION SEARCH PLUG-IN - START ***
Target DN: dc=example,dc=com       Scope: LDAP_SCOPE_SUBTREE
Dereference setting: LDAP_DEREF_NEVER
Search filter: LDAP_FILTER_SUBSTRINGS
        Attribute type: uid
        Substring# 0: go
        Substring# 1: iv
String representation of search filter: (uid=*go*iv*)
*** PREOPERATION SEARCH PLUG-IN - END ***

Building a Filter Info

When building a filter, notice that the plug-in API provides slapi_str2filter() to convert strings to Slapi_Filter data types, and slapi_filter_join() to join simple filters with boolean operators AND, OR, or NOT. Free the filter with slapi_filter_free(). Refer to Part II, Directory Server Plug-In API Reference for details.

Normal Directory Server Search Behavior

Directory Server gets a list of candidate entries, then iterates through the list to check which entries match the search criteria. The plug-in then puts the set of results in the parameter block. In most cases, searching within a plug-in is typically performed with slapi_search_internal*() calls.

Extending the Compare Operation

This section shows how to develop functionality called by Directory Server before a client compare operation. The following example logs the target DN and attribute of the entry with which to compare values. For complete example code, refer to install-path/examples/testpreop.c.


Example 6–7 Plug-In Comparison Function (testpreop.c)

#include "slapi-plugin.h"

int
testpreop_cmp(Slapi_PBlock * pb)
{
    char * dn;                         /* Target DN               */
    char * attr_type;                  /* Type of attr to compare */
    /* Attribute value could be lots of things, even a binary file.
     * Here, do not try to retrieve the value to compare.         */
    int    connId, opId, rc = 0;
    long   msgId;

    rc |= slapi_pblock_get(pb, SLAPI_COMPARE_TARGET,  &dn);
    rc |= slapi_pblock_get(pb, SLAPI_COMPARE_TYPE,    &attr_type);
    rc |= slapi_pblock_get(pb, SLAPI_OPERATION_MSGID, &msgId);
    rc |= slapi_pblock_get(pb, SLAPI_CONN_ID,         &connId);
    rc |= slapi_pblock_get(pb, SLAPI_OPERATION_ID,    &opId);
    if (rc == 0) {
        slapi_log_info_ex(
            SLAPI_LOG_INFO_AREA_PLUGIN,
            SLAPI_LOG_INFO_LEVEL_DEFAULT,
            msgId,
            connId,
            opId,
            "testpreop_cmp in test-preop plug-in",
            "Target DN: %s\tAttribute type: %s\n", dn, attr_type
        );
    }

    return (rc);
}

The plug-in function can access the attribute value to use for the comparison as well. The attribute value in the parameter block is in a berval structure. Thus, the value could be binary data such as a JPEG image. No attempt is made to write the value to the logs.

The following example shows the slapi_pblock_get() call used to obtain the attribute value.


Example 6–8 Obtaining the Attribute Value

#include "slapi-plugin.h"

int
my_compare_fn(Slapi_PBlock * pb)
{
    int             rc = 0;
    struct berval * attr_val;

    /* Obtain the attribute value from the parameter block */
    rc |= slapi_pblock_get(pb, SLAPI_COMPARE_VALUE, &attr_val);

    if (rc != 0) {
        rc = LDAP_OPERATIONS_ERROR;
        slapi_send_ldap_result(pb, rc, NULL, NULL, 0, NULL);
        return 0;
    }

    /* Do something with the value here.                   */

    return 0;
}

Before using the plug-in function as described here, set up the example suffix and register the plug-in. See Extending the Bind Operation and “To register the Plug-in”, as described previously.

Try the example plug-in function using the ldapcompare tool installed with the Directory Server Resource Kit.


Example 6–9 Performing a Comparison


$ ldapcompare sn:Jensen uid=bjensen,ou=people,dc=example,dc=com
comparing type: "sn" value: "Jensen" in entry
 "uid=bjensen,ou=people,dc=example,dc=com"
compare TRUE

The log entry in instance-path/logs/errors shows the following results, not including housekeeping information at the beginning of the log entry:

Target DN: uid=bjensen,ou=people,dc=example,dc=com	Attribute type: sn

Extending the Add Operation

This section shows how to develop functionality called by Directory Server before and after a client add operation.

Prepending a String to an Attribute

Example 39–10 prepends the string ADD to the description attribute of the entry to be added to the directory. For complete example code, refer to instance-path/examples/testpreop.c.

Before using the plug-in function as described here, set up the example suffix and register the plug-in. See Extending the Bind Operation and “To register the Plug-in”, as described previously.

To try the preoperation add function, add an entry for Quentin Cubbins who recently joined Example.com. Quentin’s LDIF entry, quentin.ldif, reads as shown in the following example.


Example 6–10 LDIF Entry


dn: uid=qcubbins,ou=People,dc=example,dc=com
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
uid: qcubbins
givenName: Quentin
sn: Cubbins
cn: Quentin Cubbins
mail: qcubbins@example.com
userPassword: qcubbins
secretary: uid=bjensen,ou=People,dc=example,dc=com
description: Entry for Quentin Cubbins

The following example performs the prepend.


Example 6–11 Prepending ADD to the Description of the Entry (testpreop.c)

#include "slapi-plugin.h"

int
testpreop_add(Slapi_PBlock * pb)
{
    Slapi_Entry * entry;               /* Entry to add            */
    Slapi_Attr  * attribute;           /* Entry attributes        */
    Slapi_Value ** values;             /* Modified, duplicate vals*/
    int         connId, opId, rc = 0;
    long        msgId;

    /* Get the entry. */
    rc |= slapi_pblock_get(pb, SLAPI_ADD_ENTRY,       &entry);
    rc |= slapi_pblock_get(pb, SLAPI_OPERATION_MSGID, &msgId);
    rc |= slapi_pblock_get(pb, SLAPI_CONN_ID,         &connId);
    rc |= slapi_pblock_get(pb, SLAPI_OPERATION_ID,    &opId);

    /* Prepend ADD to values of description attribute.            */
    rc |= slapi_entry_attr_find(entry, "description", &attribute);
    if (rc == 0) {                 /* Found a description, so...  */
        int nvals, i, count = 0;
        const Slapi_Value * value;

        slapi_attr_get_numvalues(attribute, &nvals);
        values = (Slapi_Value **)
            slapi_ch_malloc((nvals+1)*sizeof(Slapi_Value *));

        for (                          /* ...loop for value...    */
            i = slapi_attr_first_value_const(attribute, &value);
            i != -1;
            i = slapi_attr_next_value_const(attribute, i, &value)
        ) {                            /* ...prepend "ADD ".      */
            const char  * string;
            char        * tmp;
            values[count] = slapi_value_dup(value);
            string = slapi_value_get_string(values[count]);
            tmp    = slapi_ch_malloc(5+strlen(string));
            strcpy(tmp, "ADD ");
            strcpy(tmp+4, string);
            slapi_value_set_string(values[count], tmp);
            slapi_ch_free((void **)&tmp);
            ++count;
        }

        values[count] = NULL;

    } else {                           /* entry has no desc.      */
        slapi_log_info_ex(
            SLAPI_LOG_INFO_AREA_PLUGIN,
            SLAPI_LOG_INFO_LEVEL_DEFAULT,
            msgId,
            connId,
            opId,
            "testpreop_add in test-preop plug-in",
            "Entry has no description attribute.\n"
        );
    }

    rc = slapi_entry_attr_replace_sv(entry, "description", values);
    if (rc != 0) {
        slapi_log_info_ex(
            SLAPI_LOG_INFO_AREA_PLUGIN,
            SLAPI_LOG_INFO_LEVEL_DEFAULT,
            msgId,
            connId,
            opId,
            "testpreop_add in test-preop plug-in",
            "Description attribute(s) not modified.\n"
        );
    }
    slapi_valuearray_free(&values);

    return 0;
}

Add Quentin’s entry to the directory. For example, if the entry is in quentin.ldif, type:


$ ldapmodify -h localhost -p 1389 -a -f quentin.ldif \
 -D uid=kvaughan,ou=people,dc=example,dc=com -w bribery
adding new entry uid=qcubbins,ou=People,dc=example,dc=com

$ 

At this point, search the directory for Quentin’s entry.


Example 6–12 Searching for the Entry


$ ldapsearch -h localhost -p 1389 -b dc=example,dc=com uid=qcubbins
version: 1
dn: uid=qcubbins,ou=People,dc=example,dc=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
uid: qcubbins
givenName: Quentin
sn: Cubbins
cn: Quentin Cubbins
mail: qcubbins@example.com
secretary: uid=bjensen,ou=People,dc=example,dc=com
description: ADD Entry for Quentin Cubbins
$ 

Notice the value of the description attribute.

Delete Quentin’s entry so you can use it again later as an example.


$ ldapdelete -D uid=kvaughan,ou=people,dc=example,dc=com -w bribery \
 uid=qcubbins,ou=people,dc=example,dc=com

Turn off the preoperation plug-in to avoid prepending ADD to all the entries that you add.


$ dsconf disable-plugin -h localhost -p 1389 "Test Preop"
$ dsadm restart instance-path

Logging the Entry to Add

Logging the Entry to Add logs the entry to add. For complete example code, refer to instance-path/examples/testpostop.c.

Before using the plug-in function as described here, set up the example suffix and register the plug-in. See Extending the Bind Operation.

The testpostop_add() function logs the DN of the added entry and also writes the entry to a log created by the plug-in, changelog.txt. The location of changelog.txt depends on the platform, as shown in the following example. The changelog.txt file managed by the plug-in is not related to other change logs managed by Directory Server.


Example 6–13 Tracking Added Entries (testpostop.c)

#include "slapi-plugin.h"

int
testpostop_add(Slapi_PBlock * pb)
{
    char        * dn;                  /* DN of entry to add         */
    Slapi_Entry * entry;               /* Entry to add               */
    int           is_repl = 0;         /* Is this replication?       */
    int           connId, opId, rc = 0;
    long          msgId;

    rc |= slapi_pblock_get(pb, SLAPI_ADD_TARGET,              &dn);
    rc |= slapi_pblock_get(pb, SLAPI_ADD_ENTRY,               &entry);
    rc |= slapi_pblock_get(pb, SLAPI_OPERATION_MSGID,         &msgId);
    rc |= slapi_pblock_get(pb, SLAPI_CONN_ID,                 &connId);
    rc |= slapi_pblock_get(pb, SLAPI_OPERATION_ID,            &opId);
    rc |= slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_repl);

    if (rc != 0) return (rc);
    slapi_log_info_ex(
        SLAPI_LOG_INFO_AREA_PLUGIN,
        SLAPI_LOG_INFO_LEVEL_DEFAULT,
        msgId,
        connId,
        opId,
        "testpostop_add in test-postop plug-in",
        "Added entry (%s)\n", dn
    );

    /* In general, do not interfere in replication operations.       */
    /* Log the DN and the entry to the change log file.              */
    if (!is_repl) write_changelog(_ADD, dn, (void *) entry, 0);

    return (rc);
}

After activating the plug-in, add Quentin’s entry:


$ ldapmodify -a -D uid=kvaughan,ou=people,dc=example,dc=com \
 -h localhost -p 1389 -w bribery -f quentin.ldif
adding new entry uid=qcubbins,ou=People,dc=example,dc=com

$ 

Search instance-path/logs/errors for the log message. If you ignore housekeeping information, you get the following message:

Added entry (uid=qcubbins,ou=people,dc=example,dc=com)

Notice also the entry logged to changelog.txt. The entry resembles the LDIF that was added, except that the change log has time stamps attached for internal use as shown in the following example.


Example 6–14 Example changelog.txt Entry

time: 21120506172445
dn: uid=qcubbins,ou=people,dc=example,dc=com
changetype: add
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
uid: qcubbins
givenName: Quentin
sn: Cubbins
cn: Quentin Cubbins
mail: qcubbins@example.com
secretary: uid=bjensen,ou=People,dc=example,dc=com
userPassword: qcubbins
creatorsName: cn=Directory Manager
modifiersName: cn=Directory Manager
createTimestamp: 21120506152444Z
modifyTimestamp: 21120506152444Z

Extending the Modify Operation

This section shows how to develop functionality called by Directory Server after a client modify operation. A preoperation plug-in, not demonstrated here, can be found in install-path/examples/testpreop.c. Refer to install-path/examples/testpostop.c for the source code discussed here.

Before using the plug-in function as described here, set up Directory Server as described in Logging the Entry to Add, making sure to add Quentin’s entry.

The testpostop_mod() function logs the DN of the modified entry and also writes the entry to a log managed by the plug-in, changelog.txt. The location of changelog.txt depends on the platform, as shown in the source code.

The following example shows the code that performs the logging.


Example 6–15 Tracking Modified Entries (testpostop.c)

#include "slapi-plugin.h"

int
testpostop_mod(Slapi_PBlock * pb)
{
    char    *  dn;                     /* DN of entry to modify      */
    LDAPMod ** mods;                   /* Modifications to apply     */
    int        is_repl = 0;            /* Is this replication?       */
    int        connId, opId, rc = 0;
    long       msgId;

    rc |= slapi_pblock_get(pb, SLAPI_MODIFY_TARGET,           &dn);
    rc |= slapi_pblock_get(pb, SLAPI_MODIFY_MODS,             &mods);
    rc |= slapi_pblock_get(pb, SLAPI_OPERATION_MSGID,         &msgId);
    rc |= slapi_pblock_get(pb, SLAPI_CONN_ID,                 &connId);
    rc |= slapi_pblock_get(pb, SLAPI_OPERATION_ID,            &opId);
    rc |= slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_repl);
    
    if (rc != 0) return (rc);
    slapi_log_info_ex(
        SLAPI_LOG_INFO_AREA_PLUGIN,
        SLAPI_LOG_INFO_LEVEL_DEFAULT,
        msgId,
        connId,
        opId,
        "testpostop_mod in test-postop plug-in",
        "Modified entry (%s)\n", dn
    );

    /* In general, do not interfere in replication operations.       */
    /* Log the DN and the modifications made to the change log file. */
    if (!is_repl) write_changelog(_MOD, dn, (void *) mods, 0);

    return (rc);
}

First, check that Quentin’s entry is in the directory.


Example 6–16 Checking the Directory for an Entry


$ ldapsearch -h localhost -p 1389 -b dc=example,dc=com uid=qcubbins
version: 1
dn: uid=qcubbins,ou=People,dc=example,dc=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
uid: qcubbins
givenName: Quentin
sn: Cubbins
cn: Quentin Cubbins
mail: qcubbins@example.com
secretary: uid=bjensen,ou=People,dc=example,dc=com

After the plug-in is activated in Directory Server, modify Quentin’s mail address.


Example 6–17 Modifying a Mail Address


$ ldapmodify -h localhost -p 1389 \
 -D uid=kvaughan,ou=people,dc=example,dc=com -w bribery
dn: uid=qcubbins,ou=People,dc=example,dc=com
changetype: modify
replace: mail
mail: quentin@example.com
^D

Search instance-path/logs/errors for the log message. If you ignore housekeeping information, you get the following message:

Modified entry (uid=qcubbins,ou=people,dc=example,dc=com)

Notice also the information logged to changelog.txt as shown in the following example.


Example 6–18 Example changelog.txt After Modification

time: 21120506181305
dn: uid=qcubbins,ou=people,dc=example,dc=com
changetype: modify
replace: mail
mail: quentin@example.com
-
replace: modifiersname
modifiersname: cn=Directory Manager
-
replace: modifytimestamp
modifytimestamp: 21120506161305Z
-

Extending the Rename Operation

This section demonstrates functionality called by Directory Server after a client modify RDN operation. A preoperation plug-in, not demonstrated here, can be found in install-path/examples/testpreop.c. Refer to install-path/examples/testpostop.c for the source code discussed here.

Before using the plug-in function as described here, set up Directory Server as described in Logging the Entry to Add, making sure to add Quentin’s entry.

The testpostop_modrdn() function logs the DN of the modified entry and also writes the entry to a log managed by the plug-in, changelog.txt. The location of changelog.txt depends on the platform, as shown in the source code.

The following example shows the code that performs the logging.


Example 6–19 Tracking Renamed Entries (testpostop.c)

#include "slapi-plugin.h"

int
testpostop_modrdn( Slapi_PBlock *pb )
{
    char * dn;                         /* DN of entry to rename      */
    char * newrdn;                     /* New RDN                    */
    int    dflag;                      /* Delete the old RDN?        */
    int    is_repl = 0;                /* Is this replication?       */
    int    connId, opId, rc = 0;
    long   msgId;

    rc |= slapi_pblock_get(pb, SLAPI_MODRDN_TARGET,           &dn);
    rc |= slapi_pblock_get(pb, SLAPI_MODRDN_NEWRDN,           &newrdn);
    rc |= slapi_pblock_get(pb, SLAPI_MODRDN_DELOLDRDN,        &dflag);
    rc |= slapi_pblock_get(pb, SLAPI_OPERATION_MSGID,         &msgId);
    rc |= slapi_pblock_get(pb, SLAPI_CONN_ID,                 &connId);
    rc |= slapi_pblock_get(pb, SLAPI_OPERATION_ID,            &opId);
    rc |= slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_repl);

    if (rc != 0) return (rc);
    slapi_log_info_ex(
        SLAPI_LOG_INFO_AREA_PLUGIN,
        SLAPI_LOG_INFO_LEVEL_DEFAULT,
        msgId,
        connId,
        opId,
        "testpostop_modrdn in test-postop plug-in",
        "Modrdn entry (%s)\n", dn
    );

    /* In general, do not interfere in replication operations.       */
    /* Log the DN of the renamed entry, its new RDN, and the flag
     * indicating whether the old RDN was removed to the change log. */
    if (!is_repl) write_changelog(_MODRDN, dn, (void *) newrdn, dflag);

    return (rc);
}

Check that Quentin’s entry is in the directory as shown in Extending the Modify Operation.

Last weekend, Quentin decided to change his given name to Fred. His user ID is now fcubbins, so his entry must be renamed. With this plug-in activated in Directory Server, modify the entry.


Example 6–20 Renaming an Entry


$ ldapmodify -D uid=kvaughan,ou=people,dc=example,dc=com -w bribery \
 -h localhost -p 1389
dn: uid=qcubbins,ou=People,dc=example,dc=com
changetype: modify
replace: givenName
givenName: Fred

dn: uid=qcubbins,ou=People,dc=example,dc=com
changetype: modify
replace: mail
mail: fcubbins@example.com

dn: uid=qcubbins,ou=People,dc=example,dc=com
changetype: modify
replace: cn
cn: Fred Cubbins

dn: uid=qcubbins,ou=People,dc=example,dc=com
changetype: modrdn
newrdn: uid=fcubbins
deleteoldrdn: 1
^D

Search instance-path/logs/errors for the log message. If you ignore housekeeping information, you get the following message:

Modrdn entry (uid=qcubbins,ou=people,dc=example,dc=com)

Notice also the information that is logged to changelog.txt.


Example 6–21 Example changelog.txt Entry After Rename


time: 21120506184432
dn: uid=qcubbins,ou=people,dc=example,dc=com
changetype: modrdn
newrdn: uid=fcubbins
deleteoldrdn: 1

Extending the Delete Operation

This section shows how to develop functionality called by Directory Server after a client delete operation. A preoperation plug-in, not demonstrated here, can be found in install-path/examples/testpreop.c. Refer to install-path/examples/testpostop.c for the source code discussed here.

Before using the plug-in function as described here, set up Directory Server as described in Logging the Entry to Add, making sure to add Quentin’s entry.

The testpostop_modrdn() function logs the DN of the modified entry and also writes the entry to a log managed by the plug-in, changelog.txt. The location of changelog.txt depends on the platform, as shown in the source code.

The following example shows the code that performs the logging.


Example 6–22 Tracking Entry Deletion (testpostop.c)

#include "slapi-plugin.h"

int
testpostop_del( Slapi_PBlock *pb )
{
    char * dn;                         /* DN of entry to delete      */
    int    is_repl = 0;                /* Is this replication?       */
    int    connId, opId, rc = 0;
    long   msgId;

    rc |= slapi_pblock_get(pb, SLAPI_DELETE_TARGET,           &dn);
    rc |= slapi_pblock_get(pb, SLAPI_OPERATION_MSGID,         &msgId);
    rc |= slapi_pblock_get(pb, SLAPI_CONN_ID,                 &connId);
    rc |= slapi_pblock_get(pb, SLAPI_OPERATION_ID,            &opId);
    rc |= slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_repl);
    
    if (rc != 0) return (rc);
    slapi_log_info_ex(
        SLAPI_LOG_INFO_AREA_PLUGIN,
        SLAPI_LOG_INFO_LEVEL_DEFAULT,
        msgId,
        connId,
        opId,
        "testpostop_del in test-postop plug-in",
        "Deleted entry (%s)\n", dn
    );

    /* In general, do not interfere in replication operations.       */
    /* Log the DN of the deleted entry to the change log.            */
    if (!is_repl) write_changelog(_DEL, dn, NULL, 0);

    return (rc);
}

First, check that Quentin’s entry is in the directory as shown in Extending the Modify Operation.

Quentin’s name might be Fred if you have renamed the entry as described in Extending the Rename Operation.

Imagine that Quentin shouted copious verbal abuse at a key customer causing Quentin to be fired from Example.com. With this plug-in activated in Directory Server, delete his entry.

$ ldapdelete -D uid=kvaughan,ou=people,dc=example,dc=com -w bribery \
 uid=qcubbins,ou=People,dc=example,dc=com

Search instance-path/logs/errors for the log message. If you ignore housekeeping information, you get the following message:

Deleted entry (uid=qcubbins,ou=people,dc=example,dc=com)

Notice also the information logged to changelog.txt as shown in the following example.


Example 6–23 Example changelog.txt After Deletion

time: 21120506185404
dn: uid=qcubbins,ou=people,dc=example,dc=com
changetype: delete

Intercepting Information Sent to the Client

This section demonstrates functionality called by Directory Server before sending a result code, a referral, or an entry to the client application. Refer to install-path/examples/testpreop.c for the source code discussed here.

The following example shows the code that logs the operation number and user DN.


Example 6–24 Logging and Responding to the Client (testpreop.c)

#include "slapi-plugin.h"

int
testpreop_send(Slapi_PBlock * pb)
{
    Slapi_Operation * op;              /* Operation in progress   */
    char            * connDn;          /* Get DN from connection  */
    int               connId, opId, rc = 0;
    long              msgId;

    rc |= slapi_pblock_get(pb, SLAPI_OPERATION,       &op);
    rc |= slapi_pblock_get(pb, SLAPI_CONN_DN,         &connDn);
    rc |= slapi_pblock_get(pb, SLAPI_CONN_ID,         &connId);
    rc |= slapi_pblock_get(pb, SLAPI_OPERATION_MSGID, &msgId);
    rc |= slapi_pblock_get(pb, SLAPI_OPERATION_ID,    &opId);
    if (rc == 0) {
        slapi_log_info_ex(
            SLAPI_LOG_INFO_AREA_PLUGIN,
            SLAPI_LOG_INFO_LEVEL_DEFAULT,
            msgId,
            connId,
            opId,
            "testpreop_send in test-preop plug-in",
            "Operation: %d\tUser: %s\n", slapi_op_get_type(op), connDn
        );
    }
    slapi_ch_free_string(&connDn);

    return (rc);
}

Operation identifiers are defined in the plug-in header file install-path/include/slapi-plugin.h. Search for SLAPI_OPERATION_*.

Before using the plug-in function as described here, set up the example suffix and register the plug-in. See Extending the Bind Operation and “To register the Plug-in”, as described previously.

With the plug-in activated in Directory Server, perform a search as Kirsten Vaughan:


$ ldapsearch -h localhost -p 1389 -b dc=example,dc=com \
 -D uid=kvaughan,ou=people,dc=example,dc=com -w bribery \
 uid=bcubbins

Search instance-path/logs/errors for the log messages. Minus housekeeping information, the first message reflects the bind result:

Operation: 1 User: uid=kvaughan,ou=people,dc=example,dc=com

The next message reflects the search:

Operation: 4 User: uid=kvaughan,ou=people,dc=example,dc=com

Inside plug-in functions, use slapi_op_get_type() to determine the type of an operation. Refer to Part II, Directory Server Plug-In API Reference for info about slapi_op_get_type(), or see the plug-in header file install-path/include/slapi-plugin.h for a list of operation types.

Chapter 7 Handling Authentication Using Plug-Ins

This chapter explains how to write a plug-in that adds to, bypasses, or replaces the authentication mechanisms that Directory Server supports. The chapter demonstrates the use of existing mechanisms for authentication. You must adapt the code examples to meet your particular authentication requirements.


Caution – Caution –

The examples alone do not provide secure authentication methods.


This chapter covers the following topics:

How Authentication Works

This section identifies which authentication methods are available. This section also describes how Directory Server handles authentication to identify clients. Consider the Directory Server model described in this section when writing plug-ins to modify the mechanism.

Support for Standard Methods

Directory Server supports the two authentication methods described in RFC 4511. One method is simple authentication, which is rendered more secure through the use of Secure Socket Layer (SSL) for transport. The other method is SASL, whose technology is further described in RFC 2222, Simple Authentication and Security Layer (SASL). Through SASL, Directory Server supports Kerberos authentication to the LDAP server and the Directory System Agent (DSA, an X.500 term) as described in RFC 1777, Lightweight Directory Access Protocol.

Client Identification During the Bind

For LDAP clients, Directory Server keeps track of client identity through the DN the client used to connect. The server also keeps track through the authentication method and external credentials that the client uses to connect. The parameter block holds the relevant client connection information. The DN can be accessed through the SLAPI_CONN_DN and SLAPI_CONN_AUTHTYPE parameters to slapi_pblock_set() and slapi_pblock_get().

For DSML clients that connect over HTTP, Directory Server performs identity mapping for the bind. As a result, plug-ins have the same view of the client bind, regardless of the front—end protocol.

Bind Processing in Directory Server

Before Directory Server calls a preoperation bind plug-in, Directory Server completes authentication for anonymous binds, binds by the Directory Manager, and binds by replication users before calling preoperation bind functions. Thus, the server completes the bind without calling the plug-in.


Note –

For SASL authentication mechanisms, preoperation and postoperation bind functions can be called several times during processing of a single authentication request.

In fact, multiple LDAP bind operations can be used to implement the authentication mechanism, as is the case for DIGEST-MD5, for example.


How Directory Server Processes the Bind

    To process the bind, Directory Server, does the following:

  1. Parses the bind request

  2. Determines the authentication method

  3. Determines whether the bind DN is handled locally

  4. Adds request information to the parameter block

  5. Determines whether to handle the bind in the front end or to call preoperation bind plug-in functions

  6. Performs the bind or not, using information about the bind DN entry from the server back end

Following is a description of each action:

How a Plug-In Modifies Authentication

A preoperation bind function can modify Directory Server authentication in one of two ways. The plug-in either completely bypasses the comparison of incoming authentication information to authentication information stored in the directory database or implements a custom SASL mechanism.

Bypassing Authentication

Some plug-ins bypass the comparison of authentication information in the client request to authentication information in the directory. Such plug-ins return nonzero values. A value of 1 prevents the server from completing the bind after the preoperation function returns. Use this approach when you store all authentication information outside the directory, without mapping authentication identities through LDAP or the plug-in API. In addition to the other validation of the plug-in, you must verify that the plug-in works well with server access control mechanisms.

Refer to Developing a Simple Authentication Plug-In for an example.

Using Custom SASL Mechanisms

If the plug-in implements a custom SASL mechanism, clients that use that mechanism must support it as well.

Refer to Developing a SASL Authentication Plug-In for a plug-in example.

Developing a Simple Authentication Plug-In

This section shows how a preoperation bind plug-in can use the plug-in API to authenticate a user. The example used in this section obtains the appropriate bind information from the parameter block. The example then handles the authentication if the request is for LDAP_AUTH_SIMPLE, but allows the server to continue the bind if the authentication succeeds.

Notice that some binds are performed by the front end before preoperation bind functions are called.

Locating the Simple Authentication Example

The following example shows a code excerpt from the source file install-path/examples/testbind.c.


Example 7–1 Preoperation Bind Function (testbind.c)

#include "slapi-plugin.h"

int
test_bind(Slapi_PBlock * pb)
{
    char          *  dn;               /* Target DN                 */
    int              method;           /* Authentication method     */
    struct berval *  credentials;      /* Client SASL credentials   */
    Slapi_DN      *  sdn      = NULL;  /* DN used in internal srch  */
    char          *  attrs[2] = {      /* Look at userPassword only */
                                SLAPI_USERPWD_ATTR,
                                NULL
                     };
    Slapi_Entry   *  entry    = NULL;  /* Entry returned by srch    */
    Slapi_Attr    *  attr     = NULL;  /* Pwd attr in entry found   */
    int              is_repl;          /* Is this replication?      */
    int              is_intl;          /* Is this an internal op?   */
    int              connId, opId, rc = 0;
    long             msgId;
        
    /* Obtain the bind information from the parameter block.        */
    rc |= slapi_pblock_get(pb, SLAPI_BIND_TARGET,             &dn);
    rc |= slapi_pblock_get(pb, SLAPI_BIND_METHOD,             &method);
    rc |= slapi_pblock_get(pb, SLAPI_BIND_CREDENTIALS,        &credentials);
    rc |= slapi_pblock_get(pb, SLAPI_OPERATION_MSGID,         &msgId);
    rc |= slapi_pblock_get(pb, SLAPI_CONN_ID,                 &connId);
    rc |= slapi_pblock_get(pb, SLAPI_OPERATION_ID,            &opId);
    rc |= slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_repl);
    rc |= slapi_pblock_get(pb, SLAPI_IS_INTERNAL_OPERATION,   &is_intl);

    if (rc != 0) {
        slapi_log_info_ex(
            SLAPI_LOG_INFO_AREA_PLUGIN,
            SLAPI_LOG_INFO_LEVEL_DEFAULT,
            SLAPI_LOG_NO_MSGID,
            SLAPI_LOG_NO_CONNID,
            SLAPI_LOG_NO_OPID,
            "test_bind in test-bind plug-in",
            "Could not get parameters for bind operation (error %d).\n", rc
        );
        slapi_send_ldap_result(
            pb, LDAP_OPERATIONS_ERROR, NULL, NULL, 0, NULL);
        return (LDAP_OPERATIONS_ERROR);/* Server aborts bind here.  */
    }

    /* The following code handles simple authentication, where a
     * user offers a bind DN and a password for authentication.
     *
     * Handling simple authentication is a matter of finding the
     * entry corresponding to the bind DN sent in the request,
     * then if the entry is found, checking whether the password
     * sent in the request matches a value found on the
     * userPassword attribute of the entry.                         */

    /* Avoid interfering with replication or internal operations.   */
    if (!is_repl && !is_intl) switch (method) {
    case LDAP_AUTH_SIMPLE:

        /* Find the entry specified by the bind DN...               */
        sdn = slapi_sdn_new_dn_byref(dn);
        rc |= slapi_search_internal_get_entry(
            sdn,
            attrs,
            &entry,
            plugin_id
            );
        slapi_sdn_free(&sdn);

        if (rc != 0 || entry == NULL) {
            slapi_log_info_ex(
                SLAPI_LOG_INFO_AREA_PLUGIN,
                SLAPI_LOG_INFO_LEVEL_DEFAULT,
                msgId,
                connId,
                opId,
                "test_bind in test-bind plug-in",
                "Could not find entry: %s\n", dn
            );
            rc = LDAP_NO_SUCH_OBJECT;
            slapi_send_ldap_result(pb, rc, NULL, NULL, 0, NULL);
            return (rc);
        } else {
        /* ...check credentials against the userpassword...         */
            Slapi_Value    *  credval; /* Value of credentials      */
            Slapi_ValueSet * pwvalues; /* Password attribute values */
                        
            rc |= slapi_entry_attr_find(
                entry,
                SLAPI_USERPWD_ATTR,
                &attr
            );

            if (attr == NULL) {

                slapi_log_info_ex(
                    SLAPI_LOG_INFO_AREA_PLUGIN,
                    SLAPI_LOG_INFO_LEVEL_DEFAULT,
                    msgId,
                    connId,
                    opId,
                    "test_bind in test-bind plug-in",
                    "Entry %s has no userpassword.\n",
                    dn
                );
                rc = LDAP_INAPPROPRIATE_AUTH;
                slapi_send_ldap_result(pb, rc, NULL, NULL, 0, NULL);
                return (rc);
            }

            rc |= slapi_attr_get_valueset(
                attr,
                &pwvalues
            );
            if (rc != 0 || slapi_valueset_count(pwvalues) == 0) {
                slapi_log_info_ex(
                    SLAPI_LOG_INFO_AREA_PLUGIN,
                    SLAPI_LOG_INFO_LEVEL_DEFAULT,
                    msgId,
                    connId,
                    opId,
                    "test_bind in test-bind plug-in",
                    "Entry %s has no %s attribute values.\n",
                    dn, SLAPI_USERPWD_ATTR
                );
                rc = LDAP_INAPPROPRIATE_AUTH;
                slapi_send_ldap_result(pb, rc, NULL, NULL, 0, NULL);
                                slapi_valueset_free(pwvalues);
                                return (rc);
            }

            credval = slapi_value_new_berval(credentials);

            rc = slapi_pw_find_valueset(pwvalues, credval);

            slapi_value_free(&credval);
                        slapi_valueset_free(pwvalues);

            if (rc != 0) {
                slapi_log_info_ex(
                    SLAPI_LOG_INFO_AREA_PLUGIN,
                    SLAPI_LOG_INFO_LEVEL_DEFAULT,
                    msgId,
                    connId,
                    opId,
                    "test_bind in test-bind plug-in",
                    "Credentials are not correct.\n"
                );
                rc = LDAP_INVALID_CREDENTIALS;
                slapi_send_ldap_result(pb, rc, NULL, NULL, 0, NULL);
                return (rc);
            }
        }

        /* ...if successful, set authentication for the connection. */
        if (rc != 0) return (rc);
        rc |=slapi_pblock_set(pb, SLAPI_CONN_DN, slapi_ch_strdup(dn));
        rc |=slapi_pblock_set(pb, SLAPI_CONN_AUTHMETHOD, SLAPD_AUTH_SIMPLE);
        if (rc != 0) {
            slapi_log_info_ex(
                SLAPI_LOG_INFO_AREA_PLUGIN,
                SLAPI_LOG_INFO_LEVEL_DEFAULT,
                msgId,
                connId,
                opId,
                "test_bind in test-bind plug-in",
                "Failed to set connection info.\n"
            );
            rc = LDAP_OPERATIONS_ERROR;
            slapi_send_ldap_result(pb, rc, NULL, NULL, 0, NULL);
            return (rc);
        } else {
            slapi_log_info_ex(
                SLAPI_LOG_INFO_AREA_PLUGIN,
                SLAPI_LOG_INFO_LEVEL_DEFAULT,
                msgId,
                connId,
                opId,
                "test_bind in test-bind plug-in",
                "Authenticated: %s\n", dn
            );

            /* Now that authentication succeeded, the plug-in
             * returns a value greater than 0, even though the
             * authentication has been successful. A return
             * code > 0 tells the server not to continue
             * processing the bind. A return code of 0, such
             * as LDAP_SUCCESS tells the server to continue
             * processing the operation.                            */
            slapi_send_ldap_result(
                pb, LDAP_SUCCESS, NULL, NULL, 0, NULL);
            rc = 1;
        }
        break;

    /* This plug-in supports only simple authentication.            */
    case LDAP_AUTH_NONE:
        /* Anonymous binds are handled by the front-end before
         * pre-bind plug-in functions are called, so this
         * part of the code should never be reached.                */
    case LDAP_AUTH_SASL:
    default:
        slapi_log_info_ex(
            SLAPI_LOG_INFO_AREA_PLUGIN,
            SLAPI_LOG_INFO_LEVEL_DEFAULT,
            msgId,
            connId,
            opId,
            "test_bind in test-bind plug-in",
            "Plug-in does not handle auth. method: %d\n", method
        );
        rc = 0;                        /* Let server handle bind.   */
        break;
    }

    return (rc);                       /* Server stops processing
                                        * the bind if rc > 0.       */
}

The bulk of the code processes the LDAP_AUTH_SIMPLE case. In the simple authentication case, the plug-in uses the DN and the password to authenticate the user binding through plug-in API calls.

Seeing the Plug-In Work

The plug-in demonstration works by turning on informational logging for plug-ins. You read the log messages written by the plug-in at different stages in its operation. Before using the plug-in, load a few example users and data because you cannot demonstrate the functionality while binding as a directory superuser. without calling the preoperation bind functions.

ProcedureTo Set Up an Example Suffix

If you have not done so already, set up a directory instance with a suffix, dc=example,dc=com, containing data loaded from a sample LDIF file, install-path/ds6/ldif/Example.ldif.

  1. Create a new Directory Server instance.

    For example:


    $ dsadm create /local/ds
    Choose the Directory Manager password:
    Confirm the Directory Manager password:
    $ 
  2. Start the new Directory Server instance.

    For example:


    $ dsadm start /local/ds
    Server started: pid=4705
    $ 
  3. Create a suffix called dc=example,dc=com.

    For example, with long lines folded for the printed page:


    $ dsconf create-suffix -h localhost -p 1389 dc=example,dc=com
    Enter "cn=directory manager" password: 
    Certificate "CN=defaultCert, CN=hostname:1636" presented by the
     server is not trusted.
    Type "Y" to accept, "y" to accept just once,
     "n" to refuse, "d" for more details: Y
    $ 
  4. Load the sample LDIF.

    For example, with long lines folded for the printed page:


    $ dsconf import -h localhost -p 1389 \
     /opt/SUNWdsee/ds6/ldif/Example.ldif dc=example,dc=com
    Enter "cn=directory manager" password:  
    New data will override existing data of the suffix
     "dc=example,dc=com".
    Initialization will have to be performed on replicated suffixes. 
    Do you want to continue [y/n] ? y
    
    ## Index buffering enabled with bucket size 16
    ## Beginning import job...
    ## Processing file "/opt/SUNWdsee/ds6/ldif/Example.ldif"
    ## Finished scanning file "/opt/SUNWdsee/ds6/ldif/Example.ldif" (160 entries)
    ## Workers finished; cleaning up...
    ## Workers cleaned up.
    ## Cleaning up producer thread...
    ## Indexing complete.
    ## Starting numsubordinates attribute generation.
     This may take a while, please wait for further activity reports.
    ## Numsubordinates attribute generation complete. Flushing caches...
    ## Closing files...
    ## Import complete. Processed 160 entries in 5 seconds.
     (32.00 entries/sec)
    
    Task completed (slapd exit code: 0).
    $ 
See Also

You can use Directory Service Control Center to perform this task. For more information, see the Directory Service Control Center online help.

ProcedureTo Register the Plug-In

If you have not already done so, build the example plug-in library and activate both plug-in informational logging and the example plug-in.

  1. Build the plug-in.

    Hint Use install-path/examples/Makefile or install-path/examples/Makefile64.

  2. Configure Directory Server to log plug-in informational messages and load the plug-in.

    Hint Use the commands specified in the comments at the outset of the plug-in source file.

  3. Restart Directory Server.


    $ dsadm restart instance-path
    

ProcedureTo Bypass the Plug-In

The example suffix contains a number of people. If you look up the entry for one of those people, Barbara Jensen, either anonymously or as Directory Manager, the test_bind() plug-in function is never called. The plug-in therefore never logs informational messages to the errors log.

  1. Run a search that bypasses the plug-in.


    $ ldapsearch -h localhost -p 1389 -b dc=example,dc=com uid=bjensen sn 
    version: 1
    dn: uid=bjensen, ou=People, dc=example,dc=com
    sn: Jensen
    $ grep test_bind /local/ds/logs/errors
    $

    Notice that the server bypasses preoperation bind plug-ins when special users request a bind.

ProcedureTo Bind as an Example.com User

  1. Check what happens in the errors log when you bind as Barbara Jensen.


    $ ldapsearch -h localhost -p 1389 -b dc=example,dc=com \
     -D uid=bjensen,ou=people,dc=example,dc=com -w hifalutin uid=bjensen sn
    version: 1
    dn: uid=bjensen, ou=People, dc=example,dc=com
    sn: Jensen
    $ grep test_bind /local/ds/logs/errors
    [04/Jan/2006:11:34:31 +0100] - INFORMATION -
     test_bind in test-bind plug-in
     - conn=4 op=0 msgId=1 - 
     Authenticated: uid=bjensen,ou=people,dc=example,dc=com
    $ 
  2. See what happens when you bind as Barbara Jensen, but get the password wrong.


    $ ldapsearch -h localhost -p 1389 -b dc=example,dc=com \
     -D uid=bjensen,ou=people,dc=example,dc=com -w bogus uid=bjensen sn
    ldap_simple_bind: Invalid credentials
    $ grep test_bind /local/ds/logs/errors | grep -i credentials
    [04/Jan/2006:11:36:07 +0100] - INFORMATION -
     test_bind in test-bind plug-in
     - conn=5 op=0 msgId=1 -  Credentials are not correct.
    $ 

    Here, the LDAP result is interpreted correctly by the command-line client. The plug-in message to the same effect is written to the errors log.

  3. Delete Barbara's password, then try again.


    $ ldapmodify -h localhost -p 1389 \
     -D uid=kvaughan,ou=people,dc=example,dc=com -w bribery
    dn: uid=bjensen,ou=people,dc=example,dc=com
    changetype: modify
    delete: userpassword
    modifying entry uid=bjensen,ou=people,dc=example,dc=com
    ^D
    $ ldapsearch -h localhost -p 1389 -b dc=example,dc=com \
     -D uid=bjensen,ou=people,dc=example,dc=com -w - uid=bjensen sn
    Enter bind password: 
    ldap_simple_bind: Inappropriate authentication
    $ grep test_bind /local/ds/logs/errors | grep -i password
    [04/Jan/2006:11:41:25 +0100] - INFORMATION -
     test_bind in test-bind plug-in
     - conn=8 op=0 msgId=1 - 
     Entry uid=bjensen,ou=people,dc=example,dc=com has no userpassword.
    $ 

    Here, the LDAP result is displayed correctly by the command-line client. The plug-in message will provide more information about what went wrong during Barbara’s attempt to bind, no userpassword attribute values.

Developing a SASL Authentication Plug-In

This section shows how a pre-bind operation plug-in can implement a custom Simple Authentication and Security Layer (SASL) mechanism. This mechanism enables mutual authentication without affecting existing authentication mechanisms.

The example plug-in responds to SASL bind requests with a mechanism, my_sasl_mechanism. Binds with this mechanism always succeed. The example is intended only to illustrate how a SASL authentication plug-in works, not to provide a secure mechanism for use in production.

Locating SASL Examples

This overall SASL section covers the examples install-path/examples/testsaslbind.c and install-path/examples/clients/saslclient.c.

Before using the plug-in function as described here, set up an example suffix. The setup is described in Seeing the Plug-In Work. Register the plug-in as described in the comments at the beginning of the sample file.

Registering the SASL Mechanism

Register the SASL mechanism by using the same function that registers the plug-in, slapi_register_supported_saslmechanism(). The following example shows the function that registers both the plug-in and the SASL mechanism.


Example 7–2 Registering a Custom SASL Plug-In (testsaslbind.c)

#include "slapi-plugin.h"

Slapi_PluginDesc saslpdesc = {
    "test-saslbind",                   /* plug-in identifier          */
    "Sun Microsystems, Inc.",          /* vendor name                 */
    "6.0",                             /* plug-in revision number     */
    "Sample SASL pre-bind plug-in"     /* plug-in description         */
};

#define TEST_MECHANISM  "my_sasl_mechanism"
#define TEST_AUTHMETHOD SLAPD_AUTH_SASL TEST_MECHANISM

/* Register the plug-in with the server.                              */
#ifdef _WIN32
__declspec(dllexport)
#endif
int
testsasl_init(Slapi_PBlock * pb)
{
    int rc = 0;                        /* 0 means success             */
    rc |= slapi_pblock_set(
        pb,
        SLAPI_PLUGIN_VERSION,
        SLAPI_PLUGIN_CURRENT_VERSION
    );
    rc |= slapi_pblock_set(            /* Plug-in description         */
        pb,
        SLAPI_PLUGIN_DESCRIPTION,
        (void *) &saslpdesc
    );
    rc |= slapi_pblock_set(            /* Pre-op bind SASL function   */
        pb,
        SLAPI_PLUGIN_PRE_BIND_FN,
        (void *) testsasl_bind
    );
            
    /* Register the SASL mechanism.                                   */
    slapi_register_supported_saslmechanism(TEST_MECHANISM);
    return (rc);
}

Refer to Part II, Directory Server Plug-In API Reference for details on using slapi_register_supported_saslmechanism().

The plug-in function testsasl_bind() first obtains the client DN, method, SASL mechanism, and credentials. If the client does not ask to use the TEST_MECHANISM, the function returns 0 so the server can process the bind. The following example shows how the processing is done.


Example 7–3 Handling the my_sasl_mechanism Bind (testsaslbind.c)

#include "slapi-plugin.h"

#define TEST_MECHANISM  "my_sasl_mechanism"
#define TEST_AUTHMETHOD SLAPD_AUTH_SASL TEST_MECHANISM

int
testsasl_bind(Slapi_PBlock * pb)
{
    char          * dn;                /* Target DN                   */
    int             method;            /* Authentication method       */
    char          * mechanism;         /* SASL mechanism              */
    struct berval * credentials;       /* SASL client credentials     */
    struct berval   svrcreds;          /* SASL server credentials     */
    int             connId, opId, rc = 0;
    long            msgId;
        
    rc |= slapi_pblock_get(pb, SLAPI_BIND_TARGET,        &dn);
    rc |= slapi_pblock_get(pb, SLAPI_BIND_METHOD,        &method);
    rc |= slapi_pblock_get(pb, SLAPI_BIND_CREDENTIALS,   &credentials);
    rc |= slapi_pblock_get(pb, SLAPI_BIND_SASLMECHANISM, &mechanism);
    rc |= slapi_pblock_get(pb, SLAPI_OPERATION_MSGID,    &msgId);
    rc |= slapi_pblock_get(pb, SLAPI_CONN_ID,            &connId);
    rc |= slapi_pblock_get(pb, SLAPI_OPERATION_ID,       &opId);
    if (rc == 0) {
        if (mechanism == NULL || strcmp(mechanism, TEST_MECHANISM) != 0)
            return(rc);                /* Client binding another way. */
    } else {                           /* slapi_pblock_get() failed!  */
        return(rc);
    }

    /* Using the SASL mechanism that always succeeds, set conn. info. */
    rc |= slapi_pblock_set(pb, SLAPI_CONN_DN,         slapi_ch_strdup(dn));
    rc |= slapi_pblock_set(pb, SLAPI_CONN_AUTHMETHOD, TEST_AUTHMETHOD);
    if (rc != 0) {                     /* Failed to set conn. info!   */
        slapi_log_info_ex(
            SLAPI_LOG_INFO_AREA_PLUGIN,
        SLAPI_LOG_INFO_LEVEL_DEFAULT,
            msgId,
            connId,
            opId,
            "testsasl_bind in test-saslbind plug-in",
            "slapi_pblock_set() for connection information failed.\n"
        );
        slapi_send_ldap_result(
            pb, LDAP_OPERATIONS_ERROR, NULL, NULL, 0, NULL);
        return(LDAP_OPERATIONS_ERROR); /* Server tries other mechs.   */
    }

    /* Set server credentials.                                        */
    svrcreds.bv_val = "my credentials";
    svrcreds.bv_len = sizeof("my credentials") - 1;

    rc |= slapi_pblock_set(pb, SLAPI_BIND_RET_SASLCREDS, &svrcreds);
    if (rc != 0) {                     /* Failed to set credentials!  */
        slapi_log_info_ex(
            SLAPI_LOG_INFO_AREA_PLUGIN,
        SLAPI_LOG_INFO_LEVEL_DEFAULT,
            msgId,
            connId,
            opId,
            "testsasl_bind in test-saslbind plug-in",
            "slapi_pblock_set() for server credentials failed.\n"
        );
        rc |= slapi_pblock_set(pb, SLAPI_CONN_DN,         NULL);
        rc |= slapi_pblock_set(pb, SLAPI_CONN_AUTHMETHOD, SLAPD_AUTH_NONE);
        return(LDAP_OPERATIONS_ERROR); /* Server tries other mechs.   */
    }
    
    /* Send credentials to client.                                    */
    slapi_log_info_ex(
        SLAPI_LOG_INFO_AREA_PLUGIN,
        SLAPI_LOG_INFO_LEVEL_DEFAULT,
        msgId,
        connId,
        opId,
        "testsasl_bind in test-saslbind plug-in",
        "Authenticated: %s\n", dn
    );
    slapi_send_ldap_result(pb, LDAP_SUCCESS, NULL, NULL, 0, NULL);
    return 1;                          /* Server stops processing the
                                        * bind if the plug-in returns
                                        * a value greater than 0.     */
}

The preceding example sets the DN for the client connection and the authentication method, as Directory Server does for a bind. To set SLAPI_CONN_DN and SLAPI_CONN_AUTHMETHOD is the key step of the bind. Without these values in the parameter block, Directory Server handles the client request as if the bind were anonymous. Thus, the client is left with access rights that correspond to an anonymous bind. Next, according to the SASL model, Directory Server sends credentials to the client as shown.

Developing the SASL Client

To test the plug-in, you need a client that uses my_sasl_mechanism to bind. The example discussed here uses this mechanism and the client code, saslclient.c, which is delivered with Directory Server.

The client authenticates by using the example SASL mechanism for a synchronous bind to Directory Server. The following example shows how authentication proceeds on the client side for Ted Morris.


Example 7–4 Client Using my_sasl_mechanism (clients/saslclient.c)

#include "ldap.h"

/* Use the fake SASL mechanism.                                      */
#define MECH "my_sasl_mechanism"

/* Global variables for client connection information.
 * You may set these here or on the command line.                    */
static char * host = "localhost";      /* Server hostname            */
static int    port = 389;              /* Server port                */
/* Load <install-path>/ldif/Example.ldif
 * before trying the plug-in with this default user.                 */
static char * user = "uid=tmorris,ou=people,dc=example,dc=com";
/* New value for userPassword                                        */
static char * npwd = "23skidoo";

/* Check for host, port, user and new password as arguments.         */
int get_user_args(int argc, char ** argv);

int
main(int argc, char ** argv)
{
    LDAP          * ld;                /* Handle to LDAP connection  */
    LDAPMod         modPW, * mods[2];  /* For modifying the password */
    char          * vals[2];           /* Value of modified password */
    struct berval   cred;              /* Client bind credentials    */
    struct berval * srvCred;           /* Server bind credentials    */
    int             ldapVersion;

    /* Use default hostname, server port, user, and new password
     * unless they are provided as arguments on the command line.    */
    if (get_user_args(argc, argv) != 0) return 1; /* Usage error     */
    
    /* Get a handle to an LDAP connection.                           */
    printf("Getting the handle to the LDAP connection...\n");
    if ((ld = ldap_init(host, port)) == NULL) {
        perror("ldap_init");
        return 1;
    }

    /* By default, the LDAP version is set to 2.                     */
    printf("Setting the version to LDAP v3...\n");
    ldapVersion = LDAP_VERSION3;
    ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &ldapVersion);

    /* Authenticate using the example SASL mechanism.                */
    printf("Bind DN is %s...\n", user);
    printf("Binding to the server using %s...\n", MECH);
    cred.bv_val = "magic";
    cred.bv_len = sizeof("magic") - 1;
    if (ldap_sasl_bind_s(ld, user, MECH, &cred, NULL, NULL, &srvCred)) {
        ldap_perror(ld, "ldap_sasl_bind_s");
        return 1;
    }

    /* Display the credentials returned by the server.               */
    printf("Server credentials: %s\n", srvCred->bv_val);

    /* Modify the user's password.                                   */
    printf("Modifying the password...\n");
    modPW.mod_op     = LDAP_MOD_REPLACE;
    modPW.mod_type   = "userpassword";
    vals[0]          = npwd;
    vals[1]          = NULL;
    modPW.mod_values = vals;

    mods[0] = &modPW; mods[1] = NULL;

    if (ldap_modify_ext_s(ld, user, mods, NULL, NULL)) {
        ldap_perror(ld, "ldap_modify_ext_s");
        return 1;
    }

    /* Finish up.                                                    */
    ldap_unbind(ld);
    printf("Modification was successful.\n");
    return 0;
}

Trying the SASL Client

The client changes Ted's password by binding to Directory Server, then requesting a modification to the userPassword attribute value.

After activating the plug-in in the server, compile the client code, saslclient.c. Next, run the client to perform the bind and the password modification.


$ ./saslclient 
Using the following connection info:
    host:    localhost
    port:    389
    bind DN: uid=tmorris,ou=people,dc=example,dc=com
    new pwd: 23skidoo
Getting the handle to the LDAP connection...
Setting the version to LDAP v3...
Bind DN is uid=tmorris,ou=people,dc=example,dc=com...
Binding to the server using my_sasl_mechanism...
Server credentials: my credentials
Modifying the password...
Modification was successful.
$ 

On the Directory Server side, the message showing that the plug-in has authenticated the client is in the errors log.


$ grep tmorris /local/ds/logs/errors | grep -i sasl 
[04/Jan/2006:12:05:30 +0100] - INFORMATION
 - testsasl_bind in test-saslbind plug-in
 - conn=12 op=0 msgId=1 - 
 Authenticated: uid=tmorris,ou=people,dc=example,dc=com

Chapter 8 Performing Internal Operations With Plug-Ins

This chapter explains how to perform search, add, modify, modify DN, and delete operations for which no corresponding client requests exist.

Refer to Chapter 16, Function Reference, Part I for information about the plug-in API functions used in this chapter.

This chapter covers the following topics:

Using Internal Operations

This chapter shows how to use internal search, add, modify, modify RDN, and delete operations.

When to Use Internal Operations

Internal operations are internal in the sense that they are initiated not by external requests from clients, but internally by plug-ins. Use internal operation calls when your plug-in needs Directory Server to perform an operation for which no client request exists.

Issues With Internal Operations

You set up the parameter blocks and handle all memory management directly when developing with internal operations. Debug sessions with optimized binaries such as, the libraries that are delivered with the product, can be tedious. Review the code carefully. If you want to, work with a partner who can point out errors that you miss. Memory management mistakes around internal operation calls lead more quickly to incomprehensible segmentation faults than other calls in the plug-in API.

Furthermore, internal operations result in updates to some internal caches but not others. For example, changes to access control instructions cause updates to the access control cache. Internal operation changes to attributes used in CoS and roles do not cause CoS and roles caches to be updated.

Finding the Internal Operations Example

The plug-in code used in this chapter can be found in install-path/examples/internal.c.

Before Using the Internal Operations Example

Before using the example plug-in internal.c, create a new suffix, dc=example,dc=com, and populate the suffix with example data.

ProcedureTo Set Up an Example Suffix

If you have not done so already, set up a directory instance with a suffix, dc=example,dc=com, containing data loaded from a sample LDIF file, install-path/ds6/ldif/Example.ldif.

  1. Create a new Directory Server instance.

    For example:


    $ dsadm create /local/ds
    Choose the Directory Manager password:
    Confirm the Directory Manager password:
    $ 
  2. Start the new Directory Server instance.

    For example:


    $ dsadm start /local/ds
    Server started: pid=4705
    $ 
  3. Create a suffix called dc=example,dc=com.

    For example, with long lines folded for the printed page:


    $ dsconf create-suffix -h localhost -p 1389 dc=example,dc=com
    Enter "cn=directory manager" password: 
    Certificate "CN=defaultCert, CN=hostname:1636" presented by the
     server is not trusted.
    Type "Y" to accept, "y" to accept just once,
     "n" to refuse, "d" for more details: Y
    $ 
  4. Load the sample LDIF.

    For example, with long lines folded for the printed page:


    $ dsconf import -h localhost -p 1389 \
     /opt/SUNWdsee/ds6/ldif/Example.ldif dc=example,dc=com
    Enter "cn=directory manager" password:  
    New data will override existing data of the suffix
     "dc=example,dc=com".
    Initialization will have to be performed on replicated suffixes. 
    Do you want to continue [y/n] ? y
    
    ## Index buffering enabled with bucket size 16
    ## Beginning import job...
    ## Processing file "/opt/SUNWdsee/ds6/ldif/Example.ldif"
    ## Finished scanning file "/opt/SUNWdsee/ds6/ldif/Example.ldif" (160 entries)
    ## Workers finished; cleaning up...
    ## Workers cleaned up.
    ## Cleaning up producer thread...
    ## Indexing complete.
    ## Starting numsubordinates attribute generation.
     This may take a while, please wait for further activity reports.
    ## Numsubordinates attribute generation complete. Flushing caches...
    ## Closing files...
    ## Import complete. Processed 160 entries in 5 seconds.
     (32.00 entries/sec)
    
    Task completed (slapd exit code: 0).
    $ 
See Also

You can use Directory Service Control Center to perform this task. For more information, see the Directory Service Control Center online help.

Internal Add

For an internal add, you allocate space for a parameter block. You set up the parameter block for the add with slapi_add_entry_internal_set_pb(). Thus, the entry is in the parameter block when you invoke slapi_add_internal_pb(). Then you free the parameter block. The internal add consumes the entry that is passed in to the server through the parameter block.


Example 8–1 Internal Add Operation (internal.c)

#include "slapi-plugin.h"

/* Internal operations require an ID for the plug-in.                   */
static Slapi_ComponentId * plugin_id     = NULL;

int
test_internal()
{
    Slapi_DN       * sdn;              /* DN holder for internal ops    */
    Slapi_Entry    * entry;            /* Entry holder for internal ops */
    Slapi_PBlock   * pb;               /* PBlock for internal ops       */
    char           * str = NULL;       /* String holder for internal ops*/
    int              len;              /* Length of LDIF from entry     */
    int              rc;               /* Return code; 0 means success. */

    /* Check that the example suffix exists.                            */
    sdn = slapi_sdn_new_dn_byval("dc=example,dc=com");
    if (slapi_be_exist(sdn)) {
        slapi_log_info_ex(
            SLAPI_LOG_INFO_AREA_PLUGIN,
            SLAPI_LOG_INFO_LEVEL_DEFAULT,
            SLAPI_LOG_NO_MSGID,
            SLAPI_LOG_NO_CONNID,
            SLAPI_LOG_NO_OPID,
            "test_internal in test-internal plug-in",
            "Suffix (%s) does not exist, exiting.\n",
            slapi_sdn_get_dn(sdn)
        );
        slapi_sdn_free(&sdn);
        return (0);
    }
    slapi_sdn_free(&sdn);

    /*
     * Add an entry for Quentin Cubbins to the example suffix
     * using slapi_add_entry_internal().                                */
    entry = slapi_entry_alloc();
    slapi_entry_set_dn(                /* slapi_entry_set_dn()          */
        entry,                         /* requires a copy of the DN.    */
        slapi_ch_strdup("uid=qcubbins,ou=People,dc=example,dc=com")
    );
                                       /* slapi_entry_add_string()      */
                                       /* does not require a copy.      */
    slapi_entry_add_string(entry, "objectclass", "top");
    slapi_entry_add_string(entry, "objectclass", "person");
    slapi_entry_add_string(entry, "objectclass", "organizationalPerson");
    slapi_entry_add_string(entry, "objectclass", "inetOrgPerson");
    slapi_entry_add_string(entry, "uid", "qcubbins");
    slapi_entry_add_string(entry, "givenName", "Quentin");
    slapi_entry_add_string(entry, "sn", "Cubbins");
    slapi_entry_add_string(entry, "cn", "Quentin Cubbins");
    slapi_entry_add_string(entry, "mail", "qcubbins@example.com");
    slapi_entry_add_string(entry, "userPassword", "qcubbins");
    slapi_entry_add_string(entry, "secretary",
        "uid=bjensen,ou=People,dc=example,dc=com");

    pb = slapi_pblock_new();           /* Make a new PBlock...          */
    rc = slapi_add_entry_internal_set_pb(
        pb,
        entry,
        NULL,                          /* No controls                   */
        plugin_id,
        SLAPI_OP_FLAG_NEVER_CHAIN      /* Never chain this operation.   */
    );
    if (rc != 0) {
        slapi_pblock_destroy(pb);
        return (-1);
    }

    str = slapi_entry2str(entry, &len);/* Entry as LDIF for the log.
                                        * Note you have to capture this
                                        * before the internal add, during
                                        * which the entry is consumed.  */
    rc = slapi_add_internal_pb(pb);    /* Entry consumed here           */
                                       /* ... get status ...            */
    slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
    if (rc != LDAP_SUCCESS) {
        slapi_pblock_destroy(pb);
        return (-1);
    }

    slapi_pblock_destroy(pb);

    slapi_log_info_ex(
        SLAPI_LOG_INFO_AREA_PLUGIN,
        SLAPI_LOG_INFO_LEVEL_DEFAULT,
        SLAPI_LOG_NO_MSGID,
        SLAPI_LOG_NO_CONNID,
        SLAPI_LOG_NO_OPID,
        "test_internal in test-internal plug-in",
        "\nAdded entry:\n%sEntry length: %d\n", str, len
    );

    return (0);
}

Notice that the internal operation requires a plugin_id. As shown, the plug-in ID is a global variable. The plug-in ID is set during plug-in initialization, using this function:

slapi_pblock_get(pb, SLAPI_PLUGIN_IDENTITY, &plugin_id);

The parameter block, pb, is passed to the plug-in initialization function. Refer to the internal_init() function in internal.c for a sample implementation.

Internal Modify

For internal modify, you first set up an array of LDAPMod modifications. The array contains information about the attribute types to modify. The modifications also contain the attribute values. Then, as for internal add, you allocate space for a parameter block. You set up the parameter block with slapi_modify_internal_set_pb(). Then you invoke the modify operation with slapi_modify_internal_pb(). Finally, you free the memory used.


Example 8–2 Internal Modify Operation (internal.c)

This example demonstrates internal modification of a user mail address.

#include "slapi-plugin.h"

static Slapi_ComponentId * plugin_id     = NULL;

int
test_internal()
{
    Slapi_Entry    * entry;            /* Entry holder for internal ops */
    Slapi_PBlock   * pb;               /* PBlock for internal ops       */
    LDAPMod          mod_attr;         /* Attribute to modify           */
    LDAPMod        * mods[2];          /* Array of modifications        */
    char           * mail_vals[]  =    /* New mail address              */
                       {"quentin@example.com", NULL};
    int              rc;               /* Return code; 0 means success. */

    /* Modify Quentin's entry after his email address changes from
     * qcubbins@example.com to quentin@example.com.                     */
    mod_attr.mod_type   = "mail";
    mod_attr.mod_op     = LDAP_MOD_REPLACE;
    mod_attr.mod_values = mail_vals;   /* mail: quentin@example.com     */
    mods[0]             = &mod_attr;
    mods[1]             = NULL;

    pb = slapi_pblock_new();           /* Set up a PBlock...            */
    rc = slapi_modify_internal_set_pb(
        pb,
        "uid=qcubbins,ou=people,dc=example,dc=com",
        mods,
        NULL,                          /* No controls                   */
        NULL,                          /* DN rather than unique ID      */
        plugin_id,
        SLAPI_OP_FLAG_NEVER_CHAIN      /* Never chain this operation.   */
    );
    if (rc != 0) {
        slapi_pblock_destroy(pb);
        return (-1);
    }

    rc = slapi_modify_internal_pb(pb); /* Unlike internal add,          */
                                       /* nothing consumed here         */
                                       /* ... get status ...            */
    slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
    if (rc != LDAP_SUCCESS) {
        slapi_pblock_destroy(pb);
        return (-1);
    }

    slapi_pblock_destroy(pb);          /* ... clean up the PBlock.      */

    slapi_log_info_ex(
        SLAPI_LOG_INFO_AREA_PLUGIN,
        SLAPI_LOG_INFO_LEVEL_DEFAULT,
        SLAPI_LOG_NO_MSGID,
        SLAPI_LOG_NO_CONNID,
        SLAPI_LOG_NO_OPID,
        "test_internal in test-internal plug-in",
        "\nModified attribute: %s\nNew value: %s\n",
        mod_attr.mod_type, mail_vals[0]
    );

    return (0);
}

Notice that the data in mod_attr and mail_vals is still available for use after the modification. Unlike internal add, internal modify does not consume the data that you set in the parameter block.

Internal Rename and Move (Modify DN)

This section describes how to develop a plug-in to rename an entry, or to move an entry.

    An internal modify DN operation is performed in these stages:

  1. Allocate space for a parameter block.

  2. Set up the operation by using slapi_rename_internal_set_pb().

  3. Invoke the operation by using slapi_modrdn_internal_pb().

    The first stage of the operation is rename. The second stage of the operation is modrdn.

  4. Free the parameter block.


Example 8–3 Internal Rename or Move Operation (internal.c)

This example demonstrates an internal modify DN operation. Internal modify DN does not consume the data that you set in the parameter block.

#include "slapi-plugin.h"

static Slapi_ComponentId * plugin_id     = NULL;

int
test_internal()
{
    Slapi_PBlock   * pb;               /* PBlock for internal ops       */
    int              rc;               /* Return code; 0 means success. */

    pb = slapi_pblock_new();           /* Set up a PBlock again...      */
    rc = slapi_rename_internal_set_pb(
        pb,
        "uid=qcubbins,ou=people,dc=example,dc=com", /*Specify target entry*/
        "uid=fcubbins",                             /*Specify new RDN     */
        "ou=people,dc=example,dc=com",              /*Specify new superior*/
        /* The new superior is the same as the old superior.            */
        1,                             /* Delete old RDN                */
        NULL,                          /* No controls                   */
        NULL,                          /* DN rather than unique ID      */
        plugin_id,
        SLAPI_OP_FLAG_NEVER_CHAIN      /* Never chain this operation.   */
    );

    if (rc != LDAP_SUCCESS) {
        slapi_pblock_destroy(pb);
        return (-1);
    }

    rc = slapi_modrdn_internal_pb(pb); /* Like internal modify,         */
                                       /* nothing consumed here.        */
                                       /* ... get status ...            */
    slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
    if (rc != LDAP_SUCCESS) {
        slapi_pblock_destroy(pb);
        return (-1);
    }

    slapi_pblock_destroy(pb);          /* ... cleaning up.              */

    slapi_log_info_ex(
        SLAPI_LOG_INFO_AREA_PLUGIN,
        SLAPI_LOG_INFO_LEVEL_DEFAULT,
        SLAPI_LOG_NO_MSGID,
        SLAPI_LOG_NO_CONNID,
        SLAPI_LOG_NO_OPID,
        "test_internal in test-internal plug-in",
        "\nNew entry RDN: %s\n", "uid=fcubbins"
    );

    return (0);
}

Internal Search

For an internal search, use callbacks to retrieve what the server finds. The callbacks allow you to retrieve the info that would be sent back to a client application were the operation initiated by an external request: LDAP result codes, entries found, and referrals.

You set up the callback data that you want to retrieve. You also write the function that is called back by the server.


Example 8–4 Search Callback (internal.c)

This code shows how the example plug-in internal.c uses a callback to retrieve an LDIF representation of the first entry that is found. The entry is found during an internal search with slapi_entry2str() as the callback function.

#include "slapi-plugin.h"

struct cb_data {                       /* Data returned from search     */
    char * e_str;                      /* Entry as LDIF                 */
    int    e_len;                      /* Length of LDIF                */
};

int
test_internal_entry_callback(Slapi_Entry * entry, void * callback_data)
{
    struct cb_data * data;
    int              len;

    /* This callback could do something more interesting with the
     * data such as build an array of entries returned by the search.
     * Here, simply log the result.                                     */
    data        = (struct cb_data *)callback_data;
    data->e_str = slapi_entry2str(entry, &len);
    data->e_len = len;
    slapi_log_info_ex(
        SLAPI_LOG_INFO_AREA_PLUGIN,
        SLAPI_LOG_INFO_LEVEL_DEFAULT,
        SLAPI_LOG_NO_MSGID,
        SLAPI_LOG_NO_CONNID,
        SLAPI_LOG_NO_OPID,
        "test_internal_entry_callback in test-internal plug-in",
        "\nFound entry: %sLength: %d\n", data->e_str, data->e_len
    );
    return (-1);                       /* Stop at the first entry.      */
}                                      /* To continue, return 0.        */

This callback stops the search at the first entry. Your plug-in might have to deal with more than one entry being returned by the search. Therefore, consider how you want to allocate space for your data depending on what your plug-in does.

With the callback data and function implemented, you are ready to process the internal search. First, allocate space for the parameter block and your callback data, and set up the parameter block with slapi_search_internal_pb_set(). Next, invoke the search with slapi_search_internal_pb(), and also set up the callback with slapi_search_internal_callback_pb(). When you are finished, free the space that you have allocated.



Example 8–5 Internal Search Operation (internal.c)

#include "slapi-plugin.h"

static Slapi_ComponentId * plugin_id     = NULL;

int
test_internal()
{
    Slapi_PBlock   * pb;               /* PBlock for internal ops       */
    char           * srch_attrs[] =    /* Attr. to get during search    */
                       {LDAP_ALL_USER_ATTRS, NULL};
    struct cb_data   callback_data;    /* Data returned from search     */
    int              rc;               /* Return code; 0 means success. */

    slapi_log_info_ex(
        SLAPI_LOG_INFO_AREA_PLUGIN,
        SLAPI_LOG_INFO_LEVEL_DEFAULT,
        SLAPI_LOG_NO_MSGID,
        SLAPI_LOG_NO_CONNID,
        SLAPI_LOG_NO_OPID,
        "test_internal in test-internal plug-in",
        "\nSearching with base DN %s, filter %s...\n",
        "dc=example,dc=com", "(uid=fcubbins)"
    );

    pb = slapi_pblock_new();           /* Set up a PBlock...            */
    rc = slapi_search_internal_set_pb(
        pb,
        "dc=example,dc=com",           /* Base DN for search            */
        LDAP_SCOPE_SUBTREE,            /* Scope                         */
        "(uid=fcubbins)",              /* Filter                        */
        srch_attrs,                    /* Set to get all user attrs.    */
        0,                             /* Return attrs. and values      */
        NULL,                          /* No controls                   */
        NULL,                          /* DN rather than unique ID      */
        plugin_id,
        SLAPI_OP_FLAG_NEVER_CHAIN      /* Never chain this operation.   */
    );

    if (rc != LDAP_SUCCESS) {
        slapi_pblock_destroy(pb);
        return (-1);
    }

    /* Internal search puts results into the PBlock, but uses callbacks
     * to get at the data as it is turned up by the search. In this case,
     * what you want to get is the entry found by the search.           */
    rc  = slapi_search_internal_pb(pb);
    rc |= slapi_search_internal_callback_pb(
        pb,
        &callback_data,
        NULL,                          /* No result callback            */
        test_internal_entry_callback,
        NULL                           /* No referral callback          */
    );

                                       /* ... get status ...            */
    slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
    if (rc != LDAP_SUCCESS) {
        slapi_pblock_destroy(pb);
        return -1;
    }
                                       /* Free the search results when
                                        * finished with them.           */
    slapi_free_search_results_internal(pb);
    slapi_pblock_destroy(pb);          /* ... done cleaning up.         */

    return (0);
}

Here, you allocate and free callback_data locally. You can manage memory differently if you pass the data to another plug-in function.


Internal Delete

For internal delete, you allocate space for the parameter block, then set up the parameter block with slapi_delete_internal_set_pb(). You invoke the delete with slapi_delete_internal_pb(). Finally, you free the parameter block.

Internal delete does not consume the data that you set in the parameter block.

Chapter 9 Writing Entry Store and Entry Fetch Plug-Ins

This chapter covers how to write plug-ins that modify how Directory Server operates when writing entries to and reading entries from the directory database.


Note –

You can use attribute encryption instead of writing an entry store and entry fetch plug-in. See Encrypting Attribute Values in Sun Java System Directory Server Enterprise Edition 6.2 Administration Guide.


This chapter covers the following topics:

Calling Entry Store and Entry Fetch Plug-Ins

This section describes when entry store and entry fetch plug-ins are called. This section also describes how entries are expected to be handled by the plug-in.

The server calls entry store plug-in functions before writing data to a directory database. It calls entry fetch plug-in functions after reading data from the directory database. Entry store and entry fetch plug-ins may therefore typically be used to encrypt and decrypt directory entries, or to perform auditing work.

Using LDIF String Parameters

Unlike other types of plug-ins, entry store and entry fetch plug-in functions do not take a parameter block as an argument. Instead, the functions take two parameters, the LDIF string representation of an entry and the length of the string. Directory Server uses the modified LDIF string after the plug-in returns successfully. An example prototype for an entry store plug-in function is as follows:

int my_entrystore_fn(char ** entry, unsigned int * length);

Plug-in functions can manipulate the string as necessary. Entry store and entry fetch plug-ins return zero, 0, on success. When the function returns, Directory Server expects entry and length to contain the modified versions of the parameters.

Locating the Entry Store and Entry Fetch Examples

The plug-in function examples in this chapter can be found in install-path/examples/testentry.c.

The following example shows the entry store scrambling function used in this chapter. This function is called by Directory Server before writing an entry to the database.


Example 9–1 Entry Fetch Scrambler (testentry.c)

#include "slapi-plugin.h"

#ifdef _WIN32
typedef unsigned int uint;
__declspec(dllexport)
#endif
int
testentry_scramble(char ** entry, uint * len)
{
    uint i;

    /* Scramble using bitwise exclusive-or on each character.     */
    for (i = 0; i < *len - 1; i++) { (*entry)[i] ^= 0xaa; }
    
    slapi_log_info_ex(
        SLAPI_LOG_INFO_AREA_PLUGIN,
        SLAPI_LOG_INFO_LEVEL_DEFAULT,
        SLAPI_LOG_NO_MSGID,
        SLAPI_LOG_NO_CONNID,
        SLAPI_LOG_NO_OPID,
        "testentry_scramble in test-entry plug-in",
        "Entry data scrambled.\n"
    );

    return 0;
}

The following example shows the entry fetch unscrambling function used in this chapter. The function is called by the server after reading an entry from the database.


Example 9–2 Entry Fetch UnScrambler (testentry.c)

#include "slapi-plugin.h"

#ifdef _WIN32
typedef unsigned int uint;
__declspec(dllexport)
#endif
int
testentry_unscramble(char ** entry, uint * len)
{
    uint i;

    /* Return now if the entry is not scrambled.                  */
    if (!strncmp(*entry, "dn:", 3)) { return 0; }
    
    /* Unscramble using bitwise exclusive-or on each character.   */
    for (i = 0; i < *len - 1; i++) { (*entry)[i] ^= 0xaa; }
    
    slapi_log_info_ex(
        SLAPI_LOG_INFO_AREA_PLUGIN,
        SLAPI_LOG_INFO_LEVEL_DEFAULT,
        SLAPI_LOG_NO_MSGID,
        SLAPI_LOG_NO_CONNID,
        SLAPI_LOG_NO_OPID,
        "testentry_unscramble in test-entry plug-in",
        "Entry data unscrambled.\n"
    );

    return 0;
}

Notice the symmetry between the two functions. The scrambling mask, 0xaa or 10101010 in binary, makes the transformation simple to understand but not secure. A secure encryption mechanism can be significantly more complicated.

Writing a Plug-In to Encrypt Entries

This section shows how to register entry stored entry fetch plug-ins. It demonstrates how to write a plug-in that scrambles directory entries that are written to the directory database. It also demonstrates how to unscramble directory entries that are read from the directory database.


Caution – Caution –

The examples in this chapter do not constitute a secure entry storage scheme.


The following example demonstrates how entry store and entry fetch plug-ins are registered with Directory Server.

Registering Entry Store and Entry Fetch Plug-Ins

The following example demonstrates how entry store and entry fetch plug-ins are registered with Directory Server.


Example 9–3 Registering Entry Store and Entry Fetch Plug-Ins (testentry.c)

#include "slapi-plugin.h"

Slapi_PluginDesc entrypdesc = {
    "test-entry",                      /* plug-in identifier      */
    "Sun Microsystems, Inc.",          /* vendor name             */
    "6.0",                             /* plug-in revision number */
    "Sample entry store/fetch plug-in" /* plug-in description     */
};

int
testentry_init(Slapi_PBlock *pb)
{
    int rc = 0;                        /* 0 means success         */
    rc |= slapi_pblock_set(            /* Plug-in API version     */
        pb,
        SLAPI_PLUGIN_VERSION,
        SLAPI_PLUGIN_CURRENT_VERSION
    );
    rc |= slapi_pblock_set(            /* Plug-in description     */
        pb,
        SLAPI_PLUGIN_DESCRIPTION,
        (void *) &entrypdesc    );
    rc |= slapi_pblock_set(            /* Entry store function    */
        pb,
        SLAPI_PLUGIN_ENTRY_STORE_FUNC,  
        (void *) testentry_scramble
    );
    rc |= slapi_pblock_set(            /* Entry fetch function    */
        pb,
        SLAPI_PLUGIN_ENTRY_FETCH_FUNC,  
        (void *) testentry_unscramble
    );
    return rc;
}

Trying the Entry Store and Entry Fetch Examples

You demonstrate the plug-in by showing the difference between scrambled and unscrambled data in the database. You therefore do not enable the plug-in immediately. Instead, you add an entry to a new directory suffix before scrambling. You then observe the results in the database on disk. Next, you remove the entry and enable the scrambling plug-in. Then you add the same entry again. Finally, you observe the results after the entry has been scrambled.

ProcedureTo Set Up an Example Suffix

If you have not done so already, set up a directory instance with a suffix, dc=example,dc=com, containing data loaded from a sample LDIF file, install-path/ds6/ldif/Example.ldif.

  1. Create a new Directory Server instance.

    For example:


    $ dsadm create /local/ds
    Choose the Directory Manager password:
    Confirm the Directory Manager password:
    $ 
  2. Start the new Directory Server instance.

    For example:


    $ dsadm start /local/ds
    Server started: pid=4705
    $ 
  3. Create a suffix called dc=example,dc=com.

    For example, with long lines folded for the printed page:


    $ dsconf create-suffix -h localhost -p 1389 dc=example,dc=com
    Enter "cn=directory manager" password: 
    Certificate "CN=defaultCert, CN=hostname:1636" presented by the
     server is not trusted.
    Type "Y" to accept, "y" to accept just once,
     "n" to refuse, "d" for more details: Y
    $ 
  4. Load the sample LDIF.

    For example, with long lines folded for the printed page:


    $ dsconf import -h localhost -p 1389 \
     /opt/SUNWdsee/ds6/ldif/Example.ldif dc=example,dc=com
    Enter "cn=directory manager" password:  
    New data will override existing data of the suffix
     "dc=example,dc=com".
    Initialization will have to be performed on replicated suffixes. 
    Do you want to continue [y/n] ? y
    
    ## Index buffering enabled with bucket size 16
    ## Beginning import job...
    ## Processing file "/opt/SUNWdsee/ds6/ldif/Example.ldif"
    ## Finished scanning file "/opt/SUNWdsee/ds6/ldif/Example.ldif" (160 entries)
    ## Workers finished; cleaning up...
    ## Workers cleaned up.
    ## Cleaning up producer thread...
    ## Indexing complete.
    ## Starting numsubordinates attribute generation.
     This may take a while, please wait for further activity reports.
    ## Numsubordinates attribute generation complete. Flushing caches...
    ## Closing files...
    ## Import complete. Processed 160 entries in 5 seconds.
     (32.00 entries/sec)
    
    Task completed (slapd exit code: 0).
    $ 
See Also

You can use Directory Service Control Center to perform this task. For more information, see the Directory Service Control Center online help.

Looking for Strings in the Database Before Scrambling

Here, you add an entry for Quentin Cubbins to the example suffix before registering the entry store and fetch plug-in with Directory Server. You see that Quentin’s mail address is visible in the database that holds mail address attribute values. Quentin’s entry, quentin.ldif, appears as shown in the following example.


Example 9–4 LDIF Representation of an Entry

dn: uid=qcubbins,ou=People,dc=example,dc=com
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
uid: qcubbins
givenName: Quentin
sn: Cubbins
cn: Quentin Cubbins
mail: qcubbins@example.com
userPassword: qcubbins
secretary: uid=bjensen,ou=People,dc=example,dc=com

Add Quentin’s entry to the directory. For example, if the entry is in quentin.ldif, add the following:


$ ldapmodify -a -h localhost -p 1389 -f quentin.ldif \
 -D uid=kvaughan,ou=people,dc=example,dc=com -w bribery

Now look for strings in the directory database file for the mail attribute values.


Example 9–5 Attribute Values in a Database File Before Scrambling


$ cd instance-path/db/example/
$ strings example_mail.db3 | grep example.com
=qcubbins@example.com
=agodiva@example.com
=hfuddnud@example.com
=pblinn@example.com
=scooper@example.com
=bcubbins@example.com
=yyorgens@example.com

Notice that Quentin’s mail address is clearly visible if a user gains access to the database files. If the value was a credit card number, security would have been an issue.

Looking for Strings in the Database After Scrambling

Here, you add an entry for Quentin Cubbins to the example suffix after registering the entry store and fetch plug-in with Directory Server. You see that Quentin’s mail address is no longer visible in the database that holds mail address attribute values.

Before loading the plug-in, delete Quentin’s entry:


$ ldapdelete -D uid=kvaughan,ou=people,dc=example,dc=com -w bribery
 uid=qcubbins,ou=People,dc=example,dc=com

Next, configure Directory Server to load the plug-in as shown in the comments at the beginning of testentry.c, and then restart the server.

With the entry store-fetch plug-in active, add Quentin’s entry back into the directory:


$ ldapmodify -a -h localhost -p 1389 -f quentin.ldif \
-D uid=kvaughan,ou=people,dc=example,dc=com -w bribery

Now search again for strings in the directory database file for the mail attribute values.


Example 9–6 Attribute Values in a Database File After Scrambling


$ cd instance-path/db/example/
$ strings example_mail.db3 | grep example.com
=agodiva@example.com
=hfuddnud@example.com
=pblinn@example.com
=scooper@example.com
=bcubbins@example.com
=yyorgens@example.com

Notice that Quentin’s mail address value is now not visible in the directory database. Directory users who have appropriate access rights, anonymous in this simple example case, can still view the attribute during a search. The attribute and its value are emphasized in the following example.


Example 9–7 Unscrambled Search Results


$ ldapsearch -h localhost -p 1389 -b dc=example,dc=com uid=qcubbins
dn: uid=qcubbins,ou=People,dc=example,dc=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
uid: qcubbins
givenName: Quentin
sn: Cubbins
cn: Quentin Cubbins
mail: qcubbins@example.com
secretary: uid=bcubbins,ou=People,dc=example,dc=com

In this way, you see that entry store and entry fetch plug-ins affect only the way entries are stored, not the directory front end.

Chapter 10 Writing Extended Operation Plug-Ins

This chapter describes how to write plug-ins to take advantage of LDAP v3 extended operations. Extended operations are defined in RFC 4511. The RFC defines “additional operations to be defined for services not available elsewhere in this protocol, for instance digitally signed operations and results.”

This chapter covers the following topics:

Calling Extended Operation Plug-Ins

This section describes how extended operations are handled by Directory Server. This section also describes what requirements are placed on extended operation plug-ins.

Directory Server identifies extended operations by object identifiers (OIDs). Clients request an operation by sending an extended operation request, specifying the following:

Implementing an Extended Operation Plug-In

This section demonstrates how to implement a basic extended operation plug-in and client application to trigger the extended operation.

The client sends an extended operation request to the server by using the OID that identifies the extended operation, in this case 1.2.3.4. The client also sends a value that holds a string. The example plug-in responds to an extended operation request by sending the client an OID. The plug-in also sends a modified version of the string that the client sent with the request.

Locating the Extended Operation Examples

The rest of this chapter refers to the plug-in code in install-path/examples/testextendedop.c, and the client code in install-path/examples/clients/reqextop.c.

Example Extended Operation Plug-In

This section explains how the extended operation plug-in works.

Registering the Extended Operation Plug-In

Before using the plug-in function as described here, build the plug-in. Also, configure Directory Server to load the plug-in.

Notice that OID 1.2.3.4 is passed as an argument through the configuration entry. The configuration entry could specify more than one nsslapd-pluginarg attribute if the plug-in supported multiple extended operations, each identified by a distinct OID, for example.

ProcedureTo Register the Plug-In

If you have not already done so, build the example plug-in library and activate both plug-in informational logging and the example plug-in.

  1. Build the plug-in.

    Hint Use install-path/examples/Makefile or install-path/examples/Makefile64.

  2. Configure Directory Server to log plug-in informational messages and load the plug-in.

    Hint Use the commands specified in the comments at the outset of the plug-in source file.

  3. Restart Directory Server.


    $ dsadm restart instance-path
    

Initializing the Extended Operation Plug-In

As for other plug-in types, extended operation plug-ins include an initialization function that registers other functions in the plug-in with Directory Server. For extended operation plug-ins, this initialization function also registers the OIDs handled by the plug-in. The function registers OIDs by setting SLAPI_PLUGIN_EXT_OP_OIDLIST in the parameter block Directory Server, which the function passes to the initialization function.


Example 10–1 Registering Plug-In Functions and OIDs (testextendedop.c)

This example demonstrates how the OID list is built and registered.

#include "slapi-plugin.h"

Slapi_PluginDesc expdesc = {
    "test-extendedop",                 /* plug-in identifier       */
    "Sun Microsystems, Inc.",          /* vendor name              */
    "6.0",                             /* plug-in revision number  */
    "Sample extended operation plug-in"/* plug-in description      */
};

#ifdef _WIN32
__declspec(dllexport)
#endif
int
testexop_init(Slapi_PBlock * pb)
{
    char ** argv;                      /* Args from configuration  */
    int     argc;                      /* entry for plug-in.       */
    char ** oid_list;                  /* OIDs supported           */
    int     rc = 0;                    /* 0 means success          */
    int     i;

    /* Get the arguments from the configuration entry.             */
    rc |= slapi_pblock_get(pb, SLAPI_PLUGIN_ARGV, &argv);
    rc |= slapi_pblock_get(pb, SLAPI_PLUGIN_ARGC, &argc);
    if (rc != 0) {
        slapi_log_info_ex(
            SLAPI_LOG_INFO_AREA_PLUGIN,
            SLAPI_LOG_INFO_LEVEL_DEFAULT,
            SLAPI_LOG_NO_MSGID,
            SLAPI_LOG_NO_CONNID,
            SLAPI_LOG_NO_OPID,
            "testexop_init in test-extendedop plug-in",
            "Could not get plug-in arguments.\n"
        );
        return (rc);
    }

    /* Extended operation plug-ins may handle a range of OIDs.     */
    oid_list = (char **)slapi_ch_malloc((argc + 1) * sizeof(char *));
    for (i = 0; i < argc; ++i) {
        oid_list[i] = slapi_ch_strdup(argv[i]);
        slapi_log_info_ex(
            SLAPI_LOG_INFO_AREA_PLUGIN,
            SLAPI_LOG_INFO_LEVEL_DEFAULT,
            SLAPI_LOG_NO_MSGID,
            SLAPI_LOG_NO_CONNID,
            SLAPI_LOG_NO_OPID,
            "testexop_init in test-extendedop plug-in",
            "Registering plug-in for extended operation %s.\n",
            oid_list[i]
        );
    }
    oid_list[argc] = NULL;
        
    rc |= slapi_pblock_set(            /* Plug-in API version      */
        pb,
        SLAPI_PLUGIN_VERSION,
        SLAPI_PLUGIN_CURRENT_VERSION
    );
    rc |= slapi_pblock_set(            /* Plug-in description      */
        pb,
        SLAPI_PLUGIN_DESCRIPTION,
        (void *) &expdesc
    );
    rc |= slapi_pblock_set(            /* Extended op. handler     */
        pb,
        SLAPI_PLUGIN_EXT_OP_FN,
        (void *) test_extendedop
    );
    rc |= slapi_pblock_set(            /* List of OIDs handled     */
        pb,
        SLAPI_PLUGIN_EXT_OP_OIDLIST,
        oid_list
    );
    return (rc);
}

Notice that you extract OIDs from the arguments passed by Directory Server. Directory Server passes the arguments from the configuration entry to the parameter block, by using slapi_ch_strdup() on each argv[] element. The OID list is then built by allocating space for the array by using slapi_ch_malloc() and placing the OIDs in each oid_list[] element. You then register the plug-in OID list by using SLAPI_PLUGIN_EXT_OP_OIDLIST. You register the extended operation handler function, test_extendedop(), using SLAPI_PLUGIN_EXT_OP_FN as shown.

Refer to Part II, Directory Server Plug-In API Reference for details about parameter block arguments and plug-in API functions.


Handling the Extended Operation

The plug-in function test_extendedop() gets the OID and value for the operation from the client request. The function then sends the client a response, as shown in Developing the Extended Operation Client.

Notice how the function obtains the OID and value from the request by using SLAPI_EXT_OP_REQ_OID and SLAPI_EXT_OP_REQ_VALUE. The function then uses slapi_ch_malloc() to construct a string to return to the client through the pointer to a berval structure, result_bval. A different extended operation plug-in might do something entirely different at this point.

Also notice that the function sends a different OID back to the client than the OID in the client request. The OID that is sent back can be used to indicate a particular result to the client, for example. The function uses slapi_send_ldap_result() to indicate success as well as to send the OID and value to the client. The function then frees the memory that was allocated. Finally, the function returns SLAPI_PLUGIN_EXTENDED_SENT_RESULT to indicate to Directory Server that processing of the plug-in function is complete.

If the function had not sent a result code to the client, it would return an LDAP result code to Directory Server. Directory Server would then send the result code to the client.

If the function cannot handle the extended operation with the specified OID, the function returns SLAPI_PLUGIN_EXTENDED_NOT_HANDLED. Then Directory Server sends an LDAP_PROTOCOL_ERROR result code to the client.

Developing the Extended Operation Client

To test the plug-in, you need a client that requests an extended operation with OID 1.2.3.4. The example discussed here, reqextop.c, is delivered with the product.

The client sets up a short string to send to Directory Server in the extended operation request. The client then gets an LDAP connection that supports LDAP version 3, and binds to Directory Server. The client then sends an extended operation request and displays the result on STDOUT.


Example 10–2 Client Requesting an Extended Operation (clients/reqextop.c)

#include <stdlib.h>
#include "ldap.h"

/* Global variables for client connection information.
 * You may set these here or on the command line.                  */
static char * host     = "localhost";  /* Server hostname          */
static int    port     = 389;          /* Server port              */
static char * bind_DN  = "cn=Directory Manager"; /* DN to bind as  */
static char * bind_pwd = "23skidoo";   /* Password for bind DN     */

/* Check for connection info as command line arguments.            */
int get_user_args(int argc, char ** argv);

int
main(int argc, char ** argv)
{
    /* OID of the extended operation that you are requesting       */
    const char    * oidrequest = "1.2.3.4"; /* Ext op OID          */
    char          * oidresult;         /* OID in reply from server */
    struct berval   valrequest;        /* Request sent             */
    struct berval * valresult;         /* Reply received           */
    LDAP          * ld;                /* Handle to connection     */
    int             version;           /* LDAP version             */

    /* Use default connection arguments unless all four are
     * provided as arguments on the command line.                  */
    if (get_user_args(argc, argv) != 0) return 1; /* Usage error   */

    /* Set up the value that you want to pass to the server        */
    printf("Setting up value to pass to server...\n");
    valrequest.bv_val = "My Value";
    valrequest.bv_len = strlen("My Value");

    /* Get a handle to an LDAP connection                          */
    printf("Getting the handle to the LDAP connection...\n");
    if ((ld = ldap_init(host, port)) == NULL) {
        perror("ldap_init");
        ldap_unbind(ld);
        return 1;
    }

    /* Set the LDAP protocol version supported by the client
       to 3. (By default, this is set to 2. Extended operations
       are part of version 3 of the LDAP protocol.)                */
    printf("Resetting version %d to 3.0...\n", version);
    version = LDAP_VERSION3;
    ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version);

    /* Authenticate to the directory as the Directory Manager      */
    printf("Binding to the directory...\n");
    if (ldap_simple_bind_s(ld, bind_DN, bind_pwd) != LDAP_SUCCESS) {
        ldap_perror(ld, "ldap_simple_bind_s");
        ldap_unbind(ld);
        return 1;
    }

    /* Initiate the extended operation                             */
    printf( "Initiating the extended operation...\n" );
    if (ldap_extended_operation_s(
            ld,
            oidrequest,
            &valrequest,
            NULL,
            NULL,
            &oidresult,
            &valresult
        ) != LDAP_SUCCESS) {
        ldap_perror(ld, "ldap_extended_operation_s failed: ");
        ldap_unbind(ld);
        return 1;
    }

    /* Get OID and value from result returned by server.           */
    printf("Operation successful.\n");
    printf("\tReturned OID: %s\n", oidresult);
    printf("\tReturned value: %s\n", valresult->bv_val);

    /* Disconnect from the server.                                 */
    ldap_unbind(ld);
    return 0;
}

In this example, the client identifies the request by OID. Notice also that the client resets the protocol version to LDAP_VERSION3 to ensure extended operation support in the protocol. The value that the client sends with the request, valrequest, points to a berval structure. Also notice that the calls used for the bind and extended operation are synchronous. Asynchronous versions are also available.


Trying the Extended Operation Example

  1. After activating the plug-in in the server, compile the client code reqextop.c.

  2. Add an entry under cn=features in the configuration to let users access the extended operation.


    $ cat myextop.ldif 
    dn: oid=1.2.3.4,cn=features,cn=config
    objectClass: top
    objectClass: directoryServerFeature
    oid: 1.2.3.4
    cn: Fake extended operation
    aci: (targetattr != "aci")(version 3.0;acl "Fake extended operation"; allow
     ( read, search, compare, proxy ) userdn = "ldap:///all";)
    
    $ ldapmodify -h localhost -p 389 -a -D cn=directory\ manager -w - -f myextop.ldif
    Enter bind password: 
    adding new entry uid=qcubbins,ou=People,dc=example,dc=com
    
    $ dsadm restart /local/ds
    Waiting for server to stop...
    Server stopped
    Server started: pid=5658
    $ 
  3. Run the client to send the extended operation request and display the result.


    $ ./reqextop -w password
    Using the following connection info:
            host:    localhost
            port:    389
            bind DN: cn=Directory Manager
            pwd:     password
    Setting up value to pass to server...
    Getting the handle to the LDAP connection...
    Resetting version 2 to 3.0...
    Binding to the directory...
    Initiating the extended operation...
    Operation successful.
            Returned OID: 5.6.7.8
            Returned value: Value from client: My Value
  4. On the Directory Server side, turn on logging.

    Messages similar to the following in the errors log show that the plug-in handled the client request:


    [22/May/2112:08:54:15 +0200] - INFORMATION -
     test_extendedop in test-extendedop plug-in - conn=0 op=1 msgId=2 -
     Request with OID: 1.2.3.4  Value from client: My Value
    [22/May/2112:08:54:15 +0200] - INFORMATION -
     test_extendedop in test-extendedop plug-in - conn=0 op=1 msgId=2 -
     OID sent to client: 5.6.7.8
     Value sent to client: 
     Value from client: My Value

You have thus demonstrated that the example extended operation plug-in handles requests for the extended operation with OID 1.2.3.4.

Chapter 11 Writing Matching Rule Plug-Ins

This chapter covers plug-ins that enable Directory Server to handle custom matching rules. The server requires such matching rule plug-ins to support LDAP v3 extensible matching, such as “sounds like” searches.

Code excerpts in this chapter demonstrate a conceptually simple case exact matching scheme. Here, exact matching is a byte comparison between DirectoryString attribute values. Despite the straightforward concept, the plug-in code runs to many lines. The plug-in must provide several functions as well as wrapper functions to enable indexing, searching, and sorting.


Note –

You must understand how Directory Server uses matching rule plug-ins before trying to implement your own plug-in. Read this chapter, then read the sample plug-in code. Avoid creating a new matching rule plug-in from scratch.


This chapter covers the following topics:

How Matching Rule Plug-Ins Work

This section summarizes what matching rule plug-ins are and how Directory Server handles them.

What a Matching Rule Is

A matching rule defines a specific way to compare attribute values that have a given syntax. In other words, a matching rule defines how potentially matching attributes are compared.

Every matching rule is identified by a unique object identifier (OID) string. A client application that requests a search can specify the matching rule OID in the search filter. The OID indicates to Directory Server how to check for a match of two attribute values.

In practice, a client application might want only entries with attribute values that match the value provided exactly. The sample plug-in demonstrates how you might implement a solution for that case. Another client might want to sort entries according to the rules for a given locale. Directory Server actually uses a matching rule plug-in to handle locale—specific matching.

Chapter 11, Directory Server Internationalization Support, in Sun Java System Directory Server Enterprise Edition 6.2 Reference includes a list of matching rules. Directory Server supports the rules for internationalized searches. You can also view the list by searching the default schema.


$ ldapsearch -h localhost -p 1389 -b cn=schema cn=schema matchingRules

Requesting a Matching Rule

To request a custom matching rule on a specific attribute, a client application includes the matching rule OID in the search filter. LDAP v3 calls these search filters extensible match filters. An extensible match filter looks like the following:

(cn:2.5.13.5:=Quentin)

This filter tells the server to search for Quentin in the common name (CN) of the entries by using matching rule 2.5.13.5. The matching rule happens to be a case exact match.

The case exact matching rule plug-in OID 2.5.13.5 enables Directory Server to perform the search correctly. Directory Server calls code in this matching rule plug-in to check for matches during a search. The server also uses the code to generate indexes that accelerate case exact searches. The indexes also help to sort entries found during such searches.

What a Matching Rule Plug-In Does

A matching rule plug-in can provide code to do the following:

To enable these capabilities, the plug-in implements matching and indexing routines that Directory Server calls to handle requests involving the particular matching rule.

The plug-in also implements factory functions that specify which routine to call when handling a particular matching rule. As a result, a plug-in can support multiple matching rules. Yet, plug-ins implementing only one matching rule also require factory function code to wrap indexing and matching routines. A plug-in therefore requires many lines of code and several functions to handle even a minimal matching rule.

The following table shows all the functions that a matching rule plug-in can implement.

Table 11–1 Functions Defined in Matching Rule Plug-Ins

Type 

Parameter Block Identifier 

Required? 

Filter factory 

SLAPI_PLUGIN_MR_FILTER_CREATE_FN

Required 

Filter index, used to check an index for matches 

SLAPI_PLUGIN_MR_FILTER_INDEX_FN

Not required 

Filter match 

SLAPI_PLUGIN_MR_FILTER_MATCH_FN

Required 

Filter match reset 

SLAPI_PLUGIN_MR_FILTER_RESET_FN

If filter must be reset for reuse 

Filter object destructor 

SLAPI_PLUGIN_DESTROY_FN

If needed to free memory 

Indexer 

SLAPI_PLUGIN_MR_INDEX_FN

Not required 

Indexer factory 

SLAPI_PLUGIN_MR_INDEXER_CREATE_FN

Not required 

Indexer object destructor 

SLAPI_PLUGIN_DESTROY_FN

If needed to free memory 

Plug-in initialization function 

Not applicable, but instead specified in configuration settings 

Required 

Server shutdown (cleanup) function 

SLAPI_CLOSE_FN

Not required 

Server startup function 

SLAPI_START_FN

Not required 

Refer to Part II, Directory Server Plug-In API Reference for details about parameter block identifiers that you can use with matching rule plug-ins.

Example Matching Rule Plug-In

The example code cited in this chapter, from install-path/examples/matchingrule.c, mimics functionality installed by default with Directory Server. For this reason, you do not need to build and load the plug-in unless you modify the plug-in to implement your own matching rule. The example uses a different OID from the plug-in that is provided with Directory Server. Therefore, the sample plug-in does not interfere with the existing plug-in if you do choose to load the sample.

Configuring Matching Rule Plug-Ins

Matching rule plug-ins have type matchingrule. Directory Server plug-ins can depend on matching rule plug-ins by type. Directory Server cannot load plug-ins unless plug-ins of the type the plug-ins depend on load without error.

Refer to Plugging Libraries Into Directory Server for details on loading plug-in configuration entries into Directory Server.

Registering Matching Rule Plug-Ins

Matching rule plug-ins include factory functions. Factory functions provide function pointers to the indexing or filter match routines dynamically. You therefore register only the factory functions as part of the plug-in initialization function.

A plug-in that handles both indexing and matching registers both factory functions and a description, as shown in the following example.


Example 11–1 Registering Matching Rule Factory Functions (matchingrule.c)

#include "slapi-plugin.h"

static Slapi_PluginDesc plg_desc = {
    "caseExactMatchingRule",           /* Plug-in identifier           */
    "Sun Microsystems, Inc.",          /* Vendor name                  */
    "6.0",                             /* Plug-in revision number      */
    "Case Exact Matching Rule plug-in" /* Plug-in description          */
};

#ifdef _WIN32
__declspec(dllexport)
#endif
int
plg_init(Slapi_PBlock * pb)
{
    int                       rc;
        
    /* Matching rule factory functions are registered using
     * the parameter block structure. Other functions are then
     * registered dynamically by the factory functions.
     *
     * This means that a single matching rule plug-in may handle
     * a number of different matching rules.                           */
    rc  = slapi_pblock_set(
        pb,
        SLAPI_PLUGIN_MR_INDEXER_CREATE_FN,
        (void *)plg_indexer_create
    );
    rc |= slapi_pblock_set(
        pb,
        SLAPI_PLUGIN_MR_FILTER_CREATE_FN,
        (void *)plg_filter_create
    );
    rc |= slapi_pblock_set(
        pb,
        SLAPI_PLUGIN_DESCRIPTION,
        (void *)&plg_desc
    );

    /* Register the matching rules themselves. */

    return rc;
}

Here, plg_init() is the plug-in initialization function, plg_indexer_create() is the indexer factory, plg_filter_create() is the filter factory, and plg_desc is the plug-in description structure.

If your plug-in uses private data, set SLAPI_PLUGIN_PRIVATE in the parameter block as a pointer to the private data structure.



Example 11–2 Registering a Matching Rule (matchingrule.c)

Register matching rules by using slapi_matchingrule_register() as shown here rather than using slapi_pblock_set() in the initialization function.

#include "slapi-plugin.h"

#define PLG_DESC      "Case Exact Matching on Directory String" \
                      " [defined in X.520]"
#define PLG_NAME      "caseExactMatch"
/* This OID is for examples only and is not intended for reuse. The
 * official OID for this matching rule is 2.5.13.5.                    */
#define PLG_OID       "1.3.6.1.4.1.42.2.27.999.4.1"

#ifdef _WIN32
__declspec(dllexport)
#endif
int
plg_init(Slapi_PBlock * pb)
{
    Slapi_MatchingRuleEntry * mrentry = slapi_matchingrule_new();
    int                       rc;
        
    /* Register matching rule factory functions.*/

    /* Matching rules themselves are registered using a
     * Slapi_MatchingRuleEntry structure, not the parameter
     * block structure used when registering plug-in functions.
     *
     * This plug-in registers only one matching rule. Yours
     * may register many matching rules.                               */
    rc |= slapi_matchingrule_set(
        mrentry,
        SLAPI_MATCHINGRULE_DESC,
        (void *)slapi_ch_strdup(PLG_DESC)
    );
    rc |= slapi_matchingrule_set(
        mrentry,
        SLAPI_MATCHINGRULE_NAME,
        (void *)slapi_ch_strdup(PLG_NAME)
    );
    rc |= slapi_matchingrule_set(
        mrentry,
        SLAPI_MATCHINGRULE_OID,
        (void *)slapi_ch_strdup(PLG_OID)
    );
    rc |= slapi_matchingrule_set(
        mrentry,
        SLAPI_MATCHINGRULE_SYNTAX,     /* Here you use DirectoryString.*/
        (void *)slapi_ch_strdup("1.3.6.1.4.1.1466.115.121.1.15")
    );
    rc |= slapi_matchingrule_register(mrentry);
    slapi_matchingrule_free(&mrentry, 1);

    return rc;
}

Handling Extensible Match Filters

This section explains and demonstrates how to handle extensible match filters corresponding to a matching rule. All matching rule plug-ins must enable filter matching.

How Directory Server Handles Extensible Match Searches

    The following process describes how Directory Server handles an extensible match search.

  1. When Directory Server receives a search request containing an extensible match filter, the server calls the filter factory function in the plug-in handling the corresponding matching rule. The server passes the filter factory a parameter block containing the extensible match filter information.

  2. The filter factory function builds a filter object. The function then sets pointers to the object as well as the appropriate filter matching and filter index functions.

  3. The server attempts to look up a set of results in an index rather than searching the entire directory.

    In order to read an index, the server calls the filter index function, which helps the server locate the appropriate indexer function.

    Directory Server only considers all entries in the directory, which can slow the search considerably if one of the following is the case:

    • No existing index applies to the search

    • The plug-in specifies no indexer function

    • The plug-in generates no keys for the values specified

    Directory Server calls the indexer function to generate the keys. Refer to Indexer Function for details about that function.

  4. Directory Server builds a list of candidate entries.

  5. Directory Server verifies that the entry is in the scope of the search before returning the entry to the client as a search result.

  6. Directory Server frees memory that was allocated for the operation.

The following figure shows how Directory Server processes the search request.

Figure 11–1 Directory Server Performing an Extensible Match Search

Flow diagram shows Directory Server allocating a PBlock
for the search, finding and returning results, and freeing the PBlock.

The main filter functions are as follows:

Filter Matching Function

The filter matching function takes pointers to the filter object, the entry, and the first attribute to check for a match.

The following figure shows how Directory Server uses the filter matching function.

Figure 11–2 Filter Match Function Context

Flow diagram shows Directory Server calling the filter
match function for each candidate match.

Notice that the filter matching function returns 0 (match) -1 (no match) or an LDAP error code for each entry processed.


Example 11–3 Filter Match Function (matchingrule.c)

This example shows the filter match function for the case exact matching rule where a match occurs when the two berval structures match.

#include "slapi-plugin.h"

typedef struct plg_filter_t            /* For manipulating filter obj. */
{   char          *  f_type;           /* Attribute type to match      */
    int              f_op;             /* Type of comparison
                                        * (<, <=, ==, >=, >, substr)
                                        * for the filter.              */
    struct berval ** f_values;         /* Array of values to match     */
} plg_filter_t;

/* Check for a match against the filter.
 * Returns:  0 filter matched
 *          -1 filter did not match
 *         > 0 an LDAP Error code                                      */
static int
plg_filter_match(
    void        * obj,                 /* Matching rule object         */
    Slapi_Entry * entry,               /* Entry to match               */
    Slapi_Attr  * attr                 /* Attributes to match          */
)
{
    plg_filter_t  * fobj  = (plg_filter_t *)obj;
    struct berval * mrVal = NULL;      /* Values handled as bervals... */
    Slapi_Value   * sval;              /* ...and as Slapi_Value structs*/
    int             rc    = -1;        /* No match                     */

    if (fobj && fobj->f_type && fobj->f_values && fobj->f_values[0]) {

        mrVal = fobj->f_values[0];

        /* Iterate through the attributes, matching subtypes.          */
        for (; attr != NULL; slapi_entry_next_attr(entry, attr, &attr)) {

            char * type = NULL;        /* Attribute type to check      */

            if ((slapi_attr_get_type(attr, &type) == 0) &&
                (type != NULL)                          &&
                (slapi_attr_type_cmp(fobj->f_type, type, 2) == 0)
            ) { /* slapi_attr_type_cmp(type1, type2, ==>2<==)
                 * matches subtypes, too. Refer to the reference
                 * documentation for details.                          */

                /* Type and subtype match, so iterate through the
                 * values of the attribute.                            */
                int hint = slapi_attr_first_value(attr, &sval);
                while (hint != -1) {
                    const struct berval * val =
                        slapi_value_get_berval(sval);

                    /* The case exact matching rule
                     * compares the two bervals.
                     *
                     * Your matching rule may do
                     * lots of different checks here.                  */
                    rc = slapi_berval_cmp(val, mrVal);
                    if (rc == 0)  {    /* Successful match             */
                    /* If you have allocated memory for a custom
                     * matching rule, do not forget to release it.     */
                        return 0;
                    }

                    hint = slapi_attr_next_value(attr, hint, &sval);
                }
            }
        }
    }
    return rc;
}

Notice that checking for a case exact match involves only slapi_berval_cmp(), which performs a byte by byte comparison. Your plug-in might do something more complex for the comparison.


Subtype Matches

Notice in the code for iterating through the attributes that slapi_attr_type_cmp() takes 2, the value assigned to SLAPI_TYPE_CMP_SUBTYPE, as its third argument. The argument forces a comparison of attribute subtypes, such as the locale of an attribute, in addition to attribute types. Refer to Part II, Directory Server Plug-In API Reference for details on plug-in API functions and their arguments.

Thread Safety and Filter Matching Functions

Directory Server never calls a filter matching function for the same filter object concurrently. Directory Server can, however, call the function concurrently for different filter objects. If you use global variables, ensure that your filter matching function handles such variables safely.

Filter Index Function

The filter index function takes a parameter block from Directory Server. The function also sets pointers in the parameter block, enabling the server to read the index.


Example 11–4 Filter Index Function (matchingrule.c)

This example shows the filter index function for the case exact matching rule where the keys are the same as the values.

#include "slapi-plugin.h"

#define PLG_OID       "1.3.6.1.4.1.42.2.27.999.4.1"
#define SUBSYS        "CaseExactMatching Plugin"

typedef struct plg_filter_t            /* For manipulating filter obj. */
{   char          *  f_type;           /* Attribute type to match      */
    int              f_op;             /* Type of comparison
                                        * (<, <=, ==, >=, >, substr)
                                        * for the filter.              */
    struct berval ** f_values;         /* Array of values to match     */
} plg_filter_t;

static int
plg_filter_index(Slapi_PBlock * pb)
{
    int            rc       = LDAP_UNAVAILABLE_CRITICAL_EXTENSION;
    void         * obj      = NULL;    /* Server lets the plug-in      */
    plg_filter_t * fobj;               /* handle the object type.      */
    int            query_op = SLAPI_OP_EQUAL; /* Only exact matches    */
        
    if (!slapi_pblock_get(pb, SLAPI_PLUGIN_OBJECT, &obj)) {

        fobj = (plg_filter_t *)obj;

        /* Case exact match requires no modifications to
         * the at this point. Your plug-in may however
         * modify the object, then set it again in the
         * parameter block.                                            */
        rc  = slapi_pblock_set(        /* This is for completeness.    */
            pb,                        /* Your plug-in may modify      */
            SLAPI_PLUGIN_OBJECT,       /* the object if necessary.     */
            fobj
        );
        rc |= slapi_pblock_set(        /* Set attr type to match.      */
            pb,
            SLAPI_PLUGIN_MR_TYPE,
            fobj->f_type
        );
        rc |= slapi_pblock_set(        /* Set fcn to obtain keys.      */
            pb,
            SLAPI_PLUGIN_MR_INDEX_FN,
            (void*)plg_index_entry
        );
        rc |= slapi_pblock_set(        /* Set values to obtain keys.   */
            pb,
            SLAPI_PLUGIN_MR_VALUES,
            fobj->f_values
        );
        rc |= slapi_pblock_set(        /* This is for completeness.    */
            pb,                        /* Your plug-in may set a       */
            SLAPI_PLUGIN_MR_OID,       /* different MR OID than the    */
            PLG_OID                    /* one in the parameter block   */
        );
        rc |= slapi_pblock_set(        /* <, <=, ==, >=, >, substr     */
            pb,                        /* In this case, ==             */
            SLAPI_PLUGIN_MR_QUERY_OPERATOR,
            &query_op
        );
    }

    return rc;
}

This code uses the variables and macros that follow:


Input Parameters for Filter Index Functions

A filter index function can get a pointer to the filter object from SLAPI_PLUGIN_OBJECT in the parameter block.

Output Parameters for Filter Index Functions

A filter index function should set values for at least the following in the parameter block before returning control to Directory Server:

Refer to Chapter 18, Parameter Block Reference for details.

Thread Safety and Filter Index Functions

Directory Server never calls a filter index function for the same filter object concurrently. Directory Server can, however, call the function concurrently for different filter objects. If you use global variables, ensure that your function handles such variables safely.

Filter Factory Function

The filter factory function takes a parameter block from Directory Server. The function then sets parameters such that Directory Server can build a list of candidate entries, candidates the server checks for matches.

The following figure shows how the filter factory function operates.

Figure 11–3 Filter Factory Function Context

Flow diagram shows Directory Server calling the filter
factor function to create a filter object.

The following example shows the filter factory function for the case exact matching rule.


Example 11–5 Filter Factory Function (matchingrule.c)

#include "slapi-plugin.h"

#define PLG_OID       "1.3.6.1.4.1.42.2.27.999.4.1"
#define SUBSYS        "CaseExactMatching Plugin"

/* Functions to obtain connection information for logging.             */
static long mypblock_get_msgid( Slapi_PBlock * pb);
static int  mypblock_get_connid(Slapi_PBlock * pb);
static int  mypblock_get_opid(  Slapi_PBlock * pb);

typedef struct plg_filter_t            /* For manipulating filter obj. */
{   char          *  f_type;           /* Attribute type to match      */
    int              f_op;             /* Type of comparison
                                        * (<, <=, ==, >=, >, substr)
                                        * for the filter.              */
    struct berval ** f_values;         /* Array of values to match     */
} plg_filter_t;

static int
plg_filter_create(Slapi_PBlock * pb)
{
    int             rc       = LDAP_UNAVAILABLE_CRITICAL_EXTENSION;
    char          * mr_oid   = NULL;   /* MR OID from the server       */
    char          * mr_type  = NULL;   /* Attr type to match           */
    struct berval * mr_value = NULL;   /* Attr value to match          */
    plg_filter_t  * fobj     = NULL;   /* Object to create             */
        
    if (
        slapi_pblock_get(pb, SLAPI_PLUGIN_MR_OID, &mr_oid) ||
        (mr_oid == NULL)
    ) {
        slapi_log_error_ex(
            -1, /* errorId */
            mypblock_get_msgid(pb),
            mypblock_get_connid(pb),
            mypblock_get_opid(pb),
            SUBSYS,
            SLAPI_INTERNAL_ERROR,
            "plg_filter_create failed: NULL OID values are invalid.\n"
        );
        }
    else if (strcmp(mr_oid, PLG_OID) == 0) {
        /* The MR OID from the server is handled by this plug-in.      */
        if (
            (slapi_pblock_get(pb, SLAPI_PLUGIN_MR_TYPE,  &mr_type) == 0)  &&
            (mr_type  != NULL)                                            &&
            (slapi_pblock_get(pb, SLAPI_PLUGIN_MR_VALUE, &mr_value) == 0) &&
            (mr_value != NULL)
        ) { /* ...provide a pointer to a filter match function.        */
            int op            = SLAPI_OP_EQUAL;
            fobj              = (plg_filter_t *)slapi_ch_calloc(
                                    1,
                                    sizeof (plg_filter_t)
                                );
            fobj->f_type      = slapi_ch_strdup(mr_type);
            fobj->f_op        = op;
            fobj->f_values    = (struct berval **)slapi_ch_malloc(
                                    2 * sizeof(struct berval *)
                                );
            fobj->f_values[0] = slapi_ch_bvdup(mr_value);
            fobj->f_values[1] = NULL;

            rc  = slapi_pblock_set(        /* Set object destructor.   */
                pb,
                SLAPI_PLUGIN_DESTROY_FN,
                (void *)plg_filter_destroy
            );
            rc |= slapi_pblock_set(        /* Set object itself.       */
                pb,
                SLAPI_PLUGIN_OBJECT,
                (void  *)fobj
            );
            rc |= slapi_pblock_set(        /* Set filter match fcn.    */
                pb,
                SLAPI_PLUGIN_MR_FILTER_MATCH_FN,
                (void *)plg_filter_match
            );
            rc |= slapi_pblock_set(        /* Set sorting function.    */
                pb,
                SLAPI_PLUGIN_MR_FILTER_INDEX_FN,
                (void *)plg_filter_index
            );

            if (rc == 0) {
                slapi_log_info_ex(
                    SLAPI_LOG_INFO_AREA_PLUGIN,
                    SLAPI_LOG_INFO_LEVEL_DEFAULT,
                    mypblock_get_msgid(pb),
                    mypblock_get_connid(pb),
                    mypblock_get_opid(pb),
                    SUBSYS,
                    "plg_filter_create (oid %s; type %s) OK\n",
                    mr_oid, mr_type
                );
            } else {
                slapi_log_error_ex(
                    -1, /* errorId */
                    mypblock_get_msgid(pb),
                    mypblock_get_connid(pb),
                    mypblock_get_opid(pb),
                    SUBSYS,
                    SLAPI_INTERNAL_ERROR,
                    "plg_filter_create (oid %s; type %s) - pblock error \n",
                    mr_oid, mr_type
                );
            }
        } else { /* Missing parameters in the pblock                   */
            slapi_log_error_ex(
                -1,
                mypblock_get_msgid(pb),
                mypblock_get_connid(pb),
                mypblock_get_opid(pb),
                SUBSYS,
                "Parameter errors ",
                "plg_filter_create: invalid input pblock.\n"
            );
        }
    }

    return rc;
}

Notice that this function returns LDAP_UNAVAILABLE_CRITICAL_EXTENSION if the function does not handle the matching rule OID in the parameter block. Refer to Handling an Unknown Matching Rule.


Input Parameters for Filter Factory Functions

A filter factory function can get values for the following from the parameter block:

Output Parameters for Filter Factory Functions

An indexer factory function should set values for the following in the parameter block before returning control to Directory Server:

Refer to Chapter 18, Parameter Block Reference for details.

Thread Safety and Filter Factory Functions

This function must be thread safe. Directory Server can call this function concurrently.

Filter Object Destructor Function

A filter object destructor function frees memory that was allocated for a filter object set up by the filter factory function. Directory Server calls the destructor after the operation completes, passing the parameter block indicating the filter object as the value of SLAPI_PLUGIN_OBJECT.

Directory Server never calls a destructor for the same object concurrently.


Example 11–6 Filter Object and Destructor (matchingrule.c)

This code shows an example object and destructor.

#include "slapi-plugin.h"

typedef struct plg_filter_t            /* For manipulating filter obj. */
{   char          *  f_type;           /* Attribute type to match      */
    int              f_op;             /* Type of comparison
                                        * (<, <=, ==, >=, >, substr)
                                        * for the filter.              */
    struct berval ** f_values;         /* Array of values to match     */
} plg_filter_t;


/* Free memory allocated for the filter object.                        */
static int
plg_filter_destroy(Slapi_PBlock * pb)
{
    void         * obj  = NULL;        /* Server lets the plug-in      */
    plg_filter_t * fobj = NULL;        /* handle the object type.      */

    if (!slapi_pblock_get(pb, SLAPI_PLUGIN_OBJECT, &obj))
    {
        fobj = (plg_filter_t *)obj;
        if (fobj){
            slapi_ch_free((void **)&fobj->f_type);
                if (fobj->f_values){
                    ber_bvecfree(fobj->f_values);
                }
            slapi_ch_free((void **)&fobj);
        }
    }
    return 0;
}

Indexing Entries According to a Matching Rule

This section covers how to provide plug-in support for a directory index that is based on a matching rule. Matching rule plug-ins are not required to enable indexing for the matching rules the plug-ins support.


Note –

Directory Server can use your plug-in for extensible match searches even if you provide no indexing capability.

Without support for indexing, however, Directory Server must generate search results from the entire directory, which severely impacts search performance.


Directory indexes speed up client searches. Directory Server can build a list of search result entries by looking through the index rather than the entire directory. When directories contain many entries, indexes offer a considerable search performance boost. Directory administrators therefore configure the directory to maintain indexes for attributes that are searched regularly.

How Directory Server Handles the Index

To maintain an index for a custom matching rule supported by your plug-in, the server requires an indexer function. The indexer function is capable of translating attribute values to index keys. Directory Server calls the indexer function to create the index, and subsequently when adding, modifying, or deleting attribute values, because such changes can affect the index.

To read an index for a custom matching rule, Directory Server requires a filter index function. The filter factory function provides a pointer to this function. Refer to Handling Extensible Match Filters and Filter Index Function for details.

Directory Server relies on the indexer factory function in your plug-in to set up an indexing object and provide a pointer to the appropriate indexer function.

The following figure shows how Directory Server performs indexing based on a matching rule.

Figure 11–4 Directory Server Maintaining an Index Using a Matching Rule

Flow diagram shows Directory Server calling the indexer
function to generate index keys.

    The following summarizes the process shown in Figure 11–4.

  1. Directory Server creates a parameter block, setting parameters to indicate the matching rule OID and attribute type to match. Directory Server then calls the indexer factory function.

  2. The indexer factory function examines the parameter block and allocates an indexer object if necessary. The function also sets pointers to the indexer and filter index functions. If necessary, the function sets a pointer to a destructor to free the indexer object before returning control to Directory Server.

  3. Directory Server sets the parameter block to indicate attribute values to translate to keys and calls the indexer function.

  4. The indexer function translates values to keys. After the indexer returns, Directory Server uses the keys to update the index.

  5. Directory Server frees the parameter block. If necessary, the server frees the indexer object by using the destructor provided.

Be aware that Directory Server can call indexer factory functions concurrently. Indexer factory functions must therefore be thread safe.

The main filter functions are as follows:

Indexer Function

An indexer function takes a parameter block from Directory Server that contains a pointer to a berval array of attribute values. The function then generates a berval array of corresponding keys from the values. Finally, the function sets a pointer in the parameter block to the keys.

The following figure shows how Directory Server uses the indexer function.

Figure 11–5 Indexer Function Context

Flow diagram shows Directory Server allocating a PBlock
for the search, finding and returning results, and freeing the PBlock.

The following example shows the indexer function for the case exact matching rule where the keys are the same as the values.


Example 11–7 Indexer Function (matchingrule.c)

#include "slapi-plugin.h"

typedef struct plg_filter_t            /* For manipulating filter obj. */
{   char          *  f_type;           /* Attribute type to match      */
    int              f_op;             /* Type of comparison
                                        * (<, <=, ==, >=, >, substr)
                                        * for the filter.              */
    struct berval ** f_values;         /* Array of values to match     */
} plg_filter_t;

static int
plg_index_entry(Slapi_PBlock * pb)
{
    int              rc     = LDAP_OPERATIONS_ERROR;
    void          *  obj    = NULL;    /* Server lets the plug-in      */
    plg_filter_t  *  fobj;             /* handle the object type.      */
    struct berval ** values;           /* Values from server           */
        
    if (slapi_pblock_get(pb, SLAPI_PLUGIN_OBJECT, &obj) == 0) {
        fobj = (plg_filter_t *)obj;
        if (
            (slapi_pblock_get(pb, SLAPI_PLUGIN_MR_VALUES, &values) == 0) &&
            (values != NULL)
        ) {
                
            /* The case exact match builds the index keys
             * from the values by copying the values because
             * the keys and values are the same.
             *
             * Your matching rule may do something quite
             * different before setting the keys associated
             * with the values in the parameter block.                 */
            rc = slapi_pblock_set(     /* Set keys based on values.    */
                pb,
                SLAPI_PLUGIN_MR_KEYS,
                slapi_ch_bvecdup(values)
            );
        }
    }

    return rc;
}

Here, obj points to the indexer object, and values points to the berval array of attribute values. Notice that the function returns LDAP_OPERATIONS_ERROR on failure. Technically, LDAP_OPERATIONS_ERROR indicates bad sequencing of LDAP operations. For historical reasons, the error is used in this context to indicate an internal error.


Input Parameters for Indexers

An indexer function can get values for the following from the parameter block:

Output Parameter for Indexers

An indexer function should generate the berval array of keys. The function should set SLAPI_PLUGIN_MR_KEYS in the parameter block before returning control to Directory Server. Refer to Part II, Directory Server Plug-In API Reference for details.

Thread Safety and Indexers

Directory Server never calls an indexer for the same indexer object concurrently. Directory Server can, however, call the function concurrently for different indexer objects. If you use global variables, ensure that your function handles such variables safely.

Indexer Factory Function

The indexer factory function takes a parameter block from Directory Server. The function sets parameters such that Directory Server can update an index or sort results based on a matching rule.

The following figure shows how Directory Server uses the indexer factory function.

Figure 11–6 Indexer Factory Function Context

Flow diagram shows Directory Server calling the indexer
factory function to create and indexer object.

The following example shows the indexer factory function for the case exact matching rule.


Example 11–8 Indexer Factory Function (matchingrule.c)

#include "slapi-plugin.h"

#define PLG_OID       "1.3.6.1.4.1.42.2.27.999.4.1"
#define SUBSYS        "CaseExactMatching Plugin"

/* Functions to obtain connection information for logging.             */
static long mypblock_get_msgid( Slapi_PBlock * pb);
static int  mypblock_get_connid(Slapi_PBlock * pb);
static int  mypblock_get_opid(  Slapi_PBlock * pb);

static int
plg_indexer_create(Slapi_PBlock * pb)
{
    int    rc     = LDAP_UNAVAILABLE_CRITICAL_EXTENSION; /* Init failed*/
    char * mr_oid = NULL;              /* MR OID from the server       */
    
    if (slapi_pblock_get(pb, SLAPI_PLUGIN_MR_OID, mr_oid) ||
        (mr_oid == NULL)) {
        slapi_log_error_ex(
            -1, /* errorId */
            mypblock_get_msgid(pb),
            mypblock_get_connid(pb),
            mypblock_get_opid(pb),
            SUBSYS,
            SLAPI_INTERNAL_ERROR,
            "plg_indexer_create failed: NULL OID values are invalid.\n"
        );
    } else if (strcmp(mr_oid, PLG_OID) == 0) {
        if ( /* The MR OID from the server is handled by this plug-in. */
            (slapi_pblock_set(         /* This is for completeness.    */
                pb,                    /* Your plug-in may set a       */
                SLAPI_PLUGIN_MR_OID,   /* different OID than the one   */
                PLG_OID                /* provided by the server.      */
            ) == 0) &&
            (slapi_pblock_set(         /* Provide an appropriate       */
                pb,                    /* indexer function pointer.    */
                SLAPI_PLUGIN_MR_INDEX_FN,
                (void *)plg_index_entry
            ) == 0) &&
            (slapi_pblock_set(         /* Set the object destructor.   */
                pb,
                SLAPI_PLUGIN_DESTROY_FN,
                (void *)plg_filter_destroy
            ) == 0)) {
                rc = LDAP_SUCCESS;
        } else {
            slapi_log_error_ex(
                -1, /* errorId */
                mypblock_get_msgid(pb),
                mypblock_get_connid(pb),
                mypblock_get_opid(pb),
                SUBSYS,
                SLAPI_INTERNAL_ERROR,
                "plg_indexer_create failed %d \n", rc
            );
        }
    }
    
    return rc;
}

Here, PLG_OID is the object identifier for the matching rule. plg_index_entry() is the indexer. plg_filter_destroy() is the destructor. Notice that the function returns LDAP_UNAVAILABLE_CRITICAL_EXTENSION on failure.


Input Parameters for Indexer Factory Functions

An indexer factory function can read the values for the following from the parameter block:

Output Parameters for Indexer Factory Functions

An indexer factory function should set values for at least the following in the parameter block before returning control to Directory Server:

Refer to Chapter 18, Parameter Block Reference for details about the parameter block.

Thread Safety and Indexer Factory Functions

This function must be thread safe. Directory Server can call this function concurrently.

Indexer Object Destructor Function

An indexer object destructor function frees memory that was allocated for an indexer object set up by the indexer factory function. Directory Server calls the destructor after an index operation completes.

Indexer object destructors take a parameter block as their only argument, as do filter object destructors. The parameter block holds a pointer to the object in SLAPI_PLUGIN_OBJECT. Refer to the Filter Object Destructor Function for details.

Directory Server never calls a destructor for the same object concurrently.

Enabling Sorting According to a Matching Rule

Clients can request that Directory Server sort results from an extensible match search. This section explains how to enable sorting based on a matching rule.

How Directory Server Performs Sorting According to a Matching Rule

    Directory Server performs sorting as a variation of indexing, using the keys generated by an indexer function to sort results. The process is as follows:

  1. Directory Server creates a parameter block as for indexing, setting SLAPI_PLUGIN_MR_USAGE to SLAPI_PLUGIN_MR_USAGE_SORT, before passing the parameter block to the indexer factory function.

  2. The indexer factory function should set parameters in the parameter block as for indexing.

    If the sort function is different from the normal indexer function, ensure that the function checks the value of SLAPI_PLUGIN_MR_USAGE, and then sets SLAPI_MR_INDEXER_FN accordingly.

  3. Directory Server sets SLAPI_PLUGIN_MR_VALUES in the parameter block as a pointer to the values to be sorted. Directory Server then passes the parameter block the indexer function.

  4. Directory Server sorts results based on the keys the indexer function set in SLAPI_PLUGIN_MR_KEYS.

  5. Directory Server frees memory that was allocated for the operation.

    Refer to Indexing Entries According to a Matching Rule for details on how matching rule plug-ins can allow Directory Server to perform indexing based on a matching rule.

Handling an Unknown Matching Rule

This section explains how Directory Server finds a plug-in for a matching rule OID not recognized by that Directory Server.

Internal List of Correspondences

Directory Server identifies matching rules by OID. The server keeps an internal list of which matching rule plug-ins handle which OIDs.

Directory Server uses the internal list to determine which plug-in to call when the server receives an extensible match filter search request that includes a matching rule OID. Directory Server initially builds the list as matching rule plug-ins register OIDs that the plug-ins handle using slapi_matchingrule_register() in the plug-in initialization function.

OIDs Not in the Internal List

When the server encounters a matching rule OID that is not in the list, Directory Server queries each matching rule plug-in through registered factory functions. For every matching rule plug-in, Directory Server passes a parameter block that contains the OID to the matching rule factory function. The server then checks whether the factory function has in return provided a pointer in the parameter block. This pointer identifies the appropriate indexing or matching function for the OID.

If the factory function sets a pointer to the appropriate function, Directory Server assumes that the plug-in supports the matching rule.

The following shows how Directory Server checks whether a plug-in handles a specific filter match operation. The process for indexing closely resembles the process for matching.

Figure 11–7 Finding a Matching Rule Plug-In for an Unknown OID

Flow diagram shows Directory Server trying each plug-in
to locate one that handles the unknown OID.

    The following summarizes the process shown in Figure 11–7.

  1. Directory Server finds no match in the internal list of plug-ins to handle the OID. The server therefore creates a parameter block, and sets appropriate parameters for the factory, including SLAPI_PLUGIN_MR_OID.

  2. Directory Server calls the factory function. The factory function either sets a pointer in the parameter block to the appropriate function to handle the matching rule, or the function leaves the pointer NULL.

  3. Directory Server checks the parameter block after the factory function returns. If the factory function has set a function pointer in the parameter block, Directory Server updates its internal list. The function thus indicates that the plug-in supports the matching rule operation that is associated with the OID. Otherwise, the pointer remains NULL, and Directory Server tries the process again on the next matching rule plug-in that is registered.

    If after calling all matching rule plug-ins, Directory Server has found no plug-in to handle the matching rule OID, the server returns LDAP_UNAVAILABLE_CRITICAL_EXTENSION to the client.

  4. Directory Server frees the memory that was allocated for the operation.

    Be aware that Directory Server calls plug-in factory functions for the purpose of extending the internal list of correspondences.

Chapter 12 Writing Password Storage Scheme Plug-Ins

This chapter explains how to write plug-ins that allow you to modify how Directory Server stores password attribute values.

This chapter covers the following topics:

Calling Password Storage Scheme Plug-Ins

This section describes the circumstances in which Directory Server calls password storage scheme plug-ins. This section also describes how password values are expected to be handled by the plug-ins.

Types of Password Storage Scheme Plug-Ins

Two types of password storage scheme plug-ins work with Directory Server, pwdstoragescheme and reverpwdstoragescheme. The pwdstoragescheme type is one-way. After the server encodes and stores a password, the password is not decoded. The pwdstoragescheme type therefore includes plug-in functions only for encoding passwords to be stored and for comparing incoming passwords with encoded, stored passwords. The reverpwdstoragescheme type is reversible, in that the plug-in allows Directory Server to encode and decode values. The reversible type therefore includes encode, compare, and decode plug-in functions.


Note –

This chapter covers the one-way type pwdstoragescheme plug-ins.

The reversible type is for internal use only.


Preinstalled Schemes

Existing schemes delivered with Directory Server are provided as password storage scheme plug-ins. Search cn=config for entries whose DN contains cn=Password Storage Schemes. The default password storage scheme uses the Salted Secure Hashing Algorithm (SSHA).

You can change the password storage scheme used to encode user passwords. See Chapter 7, Directory Server Password Policy, in Sun Java System Directory Server Enterprise Edition 6.2 Administration Guide for instructions.

Effect on Password Attribute Values

Password storage scheme plug-in functions act on userPassword attribute values. Directory Server registers password storage scheme plug-ins at startup. After startup, any registered, enabled password storage scheme plug-in can then be used to encode password values. The plug-ins can also be used to compare incoming passwords to the encoded values. Which plug-in Directory Server invokes depends on the password storage scheme that is used for the entry in question.

Invocation for Add and Modify Requests

Add and modify requests can imply that Directory Server encode an input password, and then store it in the directory. First, Directory Server determines the storage scheme for the password value. Next, it invokes the plug-in encode function for the appropriate scheme. The encode function returns the encoded password to Directory Server.

Invocation for Bind Requests

Bind requests imply that Directory Server compares an input password value to a stored password value. As for add and modify requests, Directory Server determines the storage scheme for the password value. Next, the server invokes the plug-in compare function for the appropriate scheme. The compare scheme returns an int that communicates to Directory Server whether the two passwords match as described in Comparing a Password.

Part of a Password Policy

Password storage scheme plug-ins typically do no more than encode passwords and compare input passwords with stored, encoded passwords. In other words, plug-ins represent only a part of a comprehensive password policy. Refer to the Sun Java System Directory Server Enterprise Edition 6.2 Deployment Planning Guide for suggestions on designing secure directory services.

Writing a Password Storage Scheme Plug-In

This section demonstrates how to write a plug-in that encodes passwords. The plug-in also allows Directory Server to compare stored passwords with passwords provided by a client application.


Caution – Caution –

The examples in this chapter do not constitute a secure password storage scheme.


The source for the example plug-in referenced in this chapter is install-path/examples/testpwdstore.c. For encoding and comparing, the plug-in performs an exclusive or with 42 on each character of the password.

Encoding a Password

When Directory Server calls a password storage scheme plug-in encode function, it passes that function an input password char * and expects an encoded password char * in return. The prototype for the example encode function, xorenc(), is as follows:

static char * xorenc(char * pwd);

Allocate space for the encoded password with slapi_ch_malloc() rather than regular malloc(). Directory Server can then terminate with an “out of memory” message if allocation fails memory with slapi_ch_free().

By convention, you prefix the encoded password with the name of the password storage scheme, enclosed in braces, { and }. In other words, the example plug-in is called XOR.

The name is declared in the example:

static char * name = "XOR";   /* Storage scheme name */

You return encoded strings prefixed with {XOR}. You also register the name with Directory Server.


Example 12–1 Encoding a userPassword Value (testpwdstore.c)

#include "slapi-plugin.h"

static char * name           ="XOR";   /* Storage scheme name */

#define PREFIX_START '{'
#define PREFIX_END   '}'

static char *
xorenc(char * pwd)
{
    char * tmp    = NULL;              /* Used for encoding   */
    char * head   = NULL;              /* Encoded password    */
    char * cipher = NULL;              /* Prefix, then pwd    */
    int i, len;
  
    /* Allocate space to build the encoded password           */
    len = strlen(pwd);
    tmp = slapi_ch_malloc(len + 1);
    if (tmp == NULL) return NULL;

    memset(tmp, '\0', len + 1);
    head = tmp;

    /* Encode. This example is not secure by any means.       */
    for (i = 0; i < len; i++, pwd++, tmp++) *tmp = *pwd ^ 42;
  
    /* Add the prefix to the cipher                           */
    if (tmp != NULL) {
        cipher = slapi_ch_malloc(3 + strlen(name) + strlen(head));
        if (cipher != NULL) {
            sprintf(cipher,"%c%s%c%s",PREFIX_START,name,PREFIX_END,head);
        }
    }
    slapi_ch_free((void **) &head);
  
    return (cipher);                   /* Server frees cipher */
}

Notice that you free only memory allocated for temporary use. Directory Server frees memory for the char * returned, not the plug-in. For details on slapi_ch_malloc() and slapi_ch_free(), see Chapter 16, Function Reference, Part I.


Comparing a Password

When Directory Server calls a password storage scheme plug-in compare function, it passes that function an input password char * and a stored, encoded password char * from the directory. The compare function returns zero, 0, if the input password matches the password from the directory. The function returns 1 otherwise. The prototype for the example compare function, xorcmp(), is therefore as follows:

static int xorcmp(char * userpwd, char * dbpwd);

Here, userpwd is the input password. dbpwd is the password from the directory. The compare function must encode the input password to compare the result to the password from the directory.


Example 12–2 Comparing a userPassword Value (testpwdstore.c)

#include "slapi-plugin.h"

static int
xorcmp(char * userpwd, char * dbpwd)
{
    /* Check the correspondence of the two char by char       */       
    int i, len = strlen(userpwd);
    for (i = 0; i < len; i++) {
        if ((userpwd[i] ^ 42) != dbpwd[i])
            return 1;                  /* Different passwords */
    }
    return 0;                          /* Identical passwords */
}

Notice that Directory Server strips the prefix from the password before passing the value to the compare function. In other words, you need not account for {XOR} in this case.


Not all encoding algorithms have such a trivial compare function.

Registering the Password Storage Scheme Plug-In

You must register four password storage scheme specific items with Directory Server:

Notice that you provide no decoding function. In this case, Directory Server does not decode user passwords after they are stored.


Example 12–3 Registering a Password Storage Scheme Plug-In (testpwdstore.c)

#include "slapi-plugin.h"

static char * name           ="XOR";   /* Storage scheme name */

static Slapi_PluginDesc desc = {
    "xor-password-storage-scheme",     /* Plug-in identifier  */
    "Sun Microsystems, Inc.",          /* Vendor name         */
    "6.0",                             /* Revision number     */
    "Exclusive-or example (XOR)"       /* Plug-in description */
};

#ifdef _WIN32
__declspec(dllexport)
#endif
int
xor_init(Slapi_PBlock * pb)
{
    int rc = 0;                        /* 0 means success     */
    rc |= slapi_pblock_set(            /* Plug-in API version */
        pb,
        SLAPI_PLUGIN_VERSION,
        (void *) SLAPI_PLUGIN_CURRENT_VERSION
    );
    rc |= slapi_pblock_set(            /* Plug-in description */
        pb,
        SLAPI_PLUGIN_DESCRIPTION,
        (void *) &desc
    );
    rc |= slapi_pblock_set(            /* Storage scheme name */
        pb,
        SLAPI_PLUGIN_PWD_STORAGE_SCHEME_NAME,
        (void *) name
    );
    rc |= slapi_pblock_set(            /* Encode password     */
        pb,
        SLAPI_PLUGIN_PWD_STORAGE_SCHEME_ENC_FN,
        (void *) xorenc
    );
    rc |= slapi_pblock_set(            /* Compare password    */
        pb,
        SLAPI_PLUGIN_PWD_STORAGE_SCHEME_CMP_FN,
        (void *) xorcmp
    );
    rc |= slapi_pblock_set(            /* Never decode pwd    */
        pb,
        SLAPI_PLUGIN_PWD_STORAGE_SCHEME_DEC_FN,
        NULL
    );
    return rc;
}

Setting Up the Password Storage Scheme Plug-In

Set up a directory instance ad build the plug-in if you have not done so already.

ProcedureTo Register the Plug-In

If you have not already done so, build the example plug-in library and activate both plug-in informational logging and the example plug-in.

  1. Build the plug-in.

    Hint Use install-path/examples/Makefile or install-path/examples/Makefile64.

  2. Configure Directory Server to log plug-in informational messages and load the plug-in.

    Hint Use the commands specified in the comments at the outset of the plug-in source file.

  3. Restart Directory Server.


    $ dsadm restart instance-path
    

ProcedureTo Set Up an Example Suffix

If you have not done so already, set up a directory instance with a suffix, dc=example,dc=com, containing data loaded from a sample LDIF file, install-path/ds6/ldif/Example.ldif.

  1. Create a new Directory Server instance.

    For example:


    $ dsadm create /local/ds
    Choose the Directory Manager password:
    Confirm the Directory Manager password:
    $ 
  2. Start the new Directory Server instance.

    For example:


    $ dsadm start /local/ds
    Server started: pid=4705
    $ 
  3. Create a suffix called dc=example,dc=com.

    For example, with long lines folded for the printed page:


    $ dsconf create-suffix -h localhost -p 1389 dc=example,dc=com
    Enter "cn=directory manager" password: 
    Certificate "CN=defaultCert, CN=hostname:1636" presented by the
     server is not trusted.
    Type "Y" to accept, "y" to accept just once,
     "n" to refuse, "d" for more details: Y
    $ 
  4. Load the sample LDIF.

    For example, with long lines folded for the printed page:


    $ dsconf import -h localhost -p 1389 \
     /opt/SUNWdsee/ds6/ldif/Example.ldif dc=example,dc=com
    Enter "cn=directory manager" password:  
    New data will override existing data of the suffix
     "dc=example,dc=com".
    Initialization will have to be performed on replicated suffixes. 
    Do you want to continue [y/n] ? y
    
    ## Index buffering enabled with bucket size 16
    ## Beginning import job...
    ## Processing file "/opt/SUNWdsee/ds6/ldif/Example.ldif"
    ## Finished scanning file "/opt/SUNWdsee/ds6/ldif/Example.ldif" (160 entries)
    ## Workers finished; cleaning up...
    ## Workers cleaned up.
    ## Cleaning up producer thread...
    ## Indexing complete.
    ## Starting numsubordinates attribute generation.
     This may take a while, please wait for further activity reports.
    ## Numsubordinates attribute generation complete. Flushing caches...
    ## Closing files...
    ## Import complete. Processed 160 entries in 5 seconds.
     (32.00 entries/sec)
    
    Task completed (slapd exit code: 0).
    $ 
See Also

You can use Directory Service Control Center to perform this task. For more information, see the Directory Service Control Center online help.

Trying the Password Storage Scheme Example

This section demonstrates the example plug-in for this chapter.

Perform a Quick Test

Plug the XOR password storage scheme into Directory Server if you have not done so already.

Before you do anything else, quickly check that Directory Server calls the plug-in encode function as expected. To perform this quick test, use the pwdhash tool. The pwdhash tool has Directory Server encode a password, then display the result.


Example 12–4 Testing the Password Storage Scheme


$ pwdhash -D /local/ds -s XOR password
{XOR}ZKYY]EXN

Do not be concerned with the exact value of the resulting encoded password. The output should, however, start with {XOR}.


As Directory Server calls the encode function dynamically, you can fix the plug-in library. Then try pwdhash without doing anything to Directory Server. If this quick test does not work, fix the example.

ProcedureTo Encode a Password With the XOR Scheme

Here, you use the XOR scheme to encode a new password for Barbara Jensen.

  1. Change the password storage scheme for the suffix to XOR.


    $ dsconf set-server-prop -h localhost -p 1389 pwd-storage-scheme:XOR
  2. Change Barbara’s password to password.

  3. View Barbara’s newly encoded password.


    $ ldapsearch -h localhost -p 1389 -b dc=example,dc=com uid=bjensen
    version: 1
    dn: uid=bjensen, ou=People, dc=example,dc=com
    cn: Barbara Jensen
    cn: Babs Jensen
    sn: Jensen
    givenName: Barbara
    objectClass: top
    objectClass: person
    objectClass: organizationalPerson
    objectClass: inetOrgPerson
    ou: Product Development
    ou: People
    l: Cupertino
    uid: bjensen
    mail: bjensen@example.com
    telephoneNumber: +1 408 555 1862
    facsimileTelephoneNumber: +1 408 555 1992
    roomNumber: 0209
    userPassword: {XOR}ZKYY]EXN

    Notice that Barbara’s password is XOR-encoded.

Compare an XOR-Encoded Password

Barbara has the right to search other entries under dc=example,dc=com. Here, you search for Kirsten Vaughan's entry as bjensen.


Example 12–5 Binding With the New Password


$ ldapsearch -h localhost -p 1389 -b dc=example,dc=com
 -D uid=bjensen,ou=People,dc=example,dc=com -w password uid=kvaughan
version: 1
dn: uid=kvaughan, ou=People, dc=example,dc=com
cn: Kirsten Vaughan
sn: Vaughan
givenName: Kirsten
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
ou: Human Resources
ou: People
l: Sunnyvale
uid: kvaughan
mail: kvaughan@example.com
telephoneNumber: +1 408 555 5625
facsimileTelephoneNumber: +1 408 555 3372
roomNumber: 2871

You know that Directory Server uses a plug-in to check Barbara’s password during the bind. Thus, Directory Server must have used the XOR plug-in because you saw that Barbara’s password was XOR-encoded. If the whole process appears to work, you can conclude that the compare function works, too.

Chapter 13 Writing Password Quality Check Plug-Ins

Directory Server is delivered with a configurable password quality check plug-in. The plug-in allows you to guard against users who change their password to a value that is susceptible to typical English-language dictionary attacks. You might decide however to extend password quality checking further. This chapter shows how to write custom plug-ins that check password quality when the password is modified.

This chapter covers the following topics:

How Directory Server Uses Password Quality Check Plug-Ins

This section explains how Directory Server password policy configurations affect password quality checks. This section also explains what Directory Server expects your password quality check plug-in to do.

Password Policy to Check Password Quality

One aspect of password policy involves refusing passwords that do not meet your quality criteria. When a user submits a password value, that value is to be stored on her entry in the userPassword(5dsat) attribute. You might want the server to ensure the password value is not easy to guess or to discover. You might also want the server to log a warning when a weak password is accepted, or even to refuse weak passwords. Determining what you want is part of setting up a password policy.

The password policy entry attribute that governs to what extent the server checks password quality is pwdCheckQuality(5dsat). When this attribute is set to cause the server to check password quality, the server can call plug-ins to do so. This chapter shows how to write a plug-in that checks password quality.

For a more extensive explanation of password policy configuration, see Chapter 7, Directory Server Password Policy, in Sun Java System Directory Server Enterprise Edition 6.2 Administration Guide.

Whether to Implement a Password Check Plug-In

If you can achieve your password policy requirements by using the strong password check plug-in provided with Directory Server, do not write your own plug-in.

The strong password check plug-in provided with Directory Server can be configured for your deployment, checking the password quality aspects that you consider essential. You can configure the following:

These configuration settings are in addition to other checks. Such checks govern password length, whether the password matches common attributes on the entry, and so forth.

In many cases, you can avoid writing your own password check plug-in.

What a Password Check Plug-In Must Do

If you decide to implement your own password check plug-in, it must be of type passwordcheck. Your plug-in must implement at least the password checking function, SLAPI_PLUGIN_PASSWDCHECK_FN. This function verifies that all passwords supplied in the parameter block array, SLAPI_PASSWDCHECK_VALS, satisfy the quality check. The SLAPI_PASSWDCHECK_VALS may contain more than one password value.

The function must return the following values, depending on the password values to check.

-1

Return this value when you must send a failure result to the client application for a reason other than quality check failure. You might return this value for example when the server is busy loading a large dictionary file. The server then would not be ready to check password quality.

When you return this value, the server aborts the add or modify operation in progress.

0

Return this value when all passwords in SLAPI_PASSWDCHECK_VALS satisfy the password quality check.

>0

Return a positive integer when at least one of the passwords in SLAPI_PASSWDCHECK_VALS fails the password quality check.

When you return a positive integer, pass an error message back through SLAPI_RESULT_TEXT in the parameter block with slapi_pblock_set(). Allocate space for the message using slapi_ch_malloc(). The server takes responsibility to free the memory allocated for the message.

The message string must start with invalid password syntax: , exactly as it is given here.

The server sends the client application a result of LDAP_CONSTRAINT_VIOLATION, 19. If, however, passwordRootdnMayBypassModsChecks(5dsat) is set to allow password administrators, including the directory root DN user, to modify passwords irrespective of password policy checks, the server nevertheless accepts modifications by that user.

In the simple case used in this chapter, the work is minimal. For real—world cases, the work might however be significant, particularly if you choose to implement your own dictionary check. If you implement your own dictionary check, the plug-in might cache the dictionary content from a large file. In this case, load the dictionary as part of a plug-in start function rather than part of plug-in initialization. You thus avoid blocking startup of the server while the cache is constructed.

Writing a Custom Password Quality Check Plug-In

This section demonstrates how to write a plug-in that performs a simple password quality check.

Only code excerpts are shown in this chapter. The complete code example can be found where you installed Directory Server, install-path/ds6/examples/pwdcheck.c.

Checking Password Values

When Directory Server receives a request to add or modify a userPassword value, the server calls the registered passwordcheck plug-in. The server passes one or more values as a set of Slapi_Value structures in the parameter block. You can retrieve these values with slapi_pblock_get().

#include "slapi-plugin.h"

static int
check_pwd(Slapi_PBlock * pb)
{
    Slapi_Value ** pwdvals = NULL;
    slapi_pblock_get(pb, SLAPI_PASSWDCHECK_VALS, &pwdvals;);
}

Your code must then return zero, 0, when password values are acceptable. Your code must return nonzero when password values are unacceptable. In the simple case where bad password values are only those equal to secret12, the code is a quick strcmp.

#include "slapi-plugin.h"

/* Reject password values equal to secret12.               */
static int
check_pwd(Slapi_PBlock * pb)
{
    Slapi_Value ** pwdvals = NULL;

…

    /* Do not check values if none exist. */
    if (pwdvals == NULL) return 0;

    for (i=0 ; pwdvals[i] != NULL; i++) {
        const char * password = slapi_value_get_string(pwdvals[i]);
        if (strcmp("secret12", password) == 0) {
            slapi_pblock_set(pb, SLAPI_RESULT_TEXT,
               slapi_ch_strdup("invalid password syntax: Bad password!"));
            return 1;
        }
    }
    return 0;
}

Here, the code has Directory Server reject a password only when its value is secret12.

Initializing the Password Check Plug-In

Password check plug-ins register a password checking function, SLAPI_PLUGIN_PASSWDCHECK_FN, during initialization with the server.

#include "slapi-plugin.h"

int
pwdcheck_init(Slapi_PBlock * pb)
{
    int rc = 0;
    rc |=   slapi_pblock_set(
                pb,
                SLAPI_PLUGIN_VERSION,
                SLAPI_PLUGIN_CURRENT_VERSION
            );

    rc |=   slapi_pblock_set(
                pb,
                SLAPI_PLUGIN_DESCRIPTION,
                (void *) &pwd_desc; /* See the code for pwd_desc. */
            );

    rc |=   slapi_pblock_set(
                pb,
                SLAPI_PLUGIN_PASSWDCHECK_FN,
                (void *) check_pwd
            );
    return rc;
}

You can implement your own dictionary check that caches the dictionary content from a large file. In this case, load the dictionary as part of a plug-in start function rather than part of plug-in initialization. Thus, you avoid blocking startup of the server while the cache is constructed.

Testing the Password Check Plug-In

You test the plug-in using sample data delivered with Directory Server You use command-line tools to setup and register the plug-in.

ProcedureTo Set Up an Example Suffix

If you have not done so already, set up a directory instance with a suffix, dc=example,dc=com, containing data loaded from a sample LDIF file, install-path/ds6/ldif/Example.ldif.

  1. Create a new Directory Server instance.

    For example:


    $ dsadm create /local/ds
    Choose the Directory Manager password:
    Confirm the Directory Manager password:
    $ 
  2. Start the new Directory Server instance.

    For example:


    $ dsadm start /local/ds
    Server started: pid=4705
    $ 
  3. Create a suffix called dc=example,dc=com.

    For example, with long lines folded for the printed page:


    $ dsconf create-suffix -h localhost -p 1389 dc=example,dc=com
    Enter "cn=directory manager" password: 
    Certificate "CN=defaultCert, CN=hostname:1636" presented by the
     server is not trusted.
    Type "Y" to accept, "y" to accept just once,
     "n" to refuse, "d" for more details: Y
    $ 
  4. Load the sample LDIF.

    For example, with long lines folded for the printed page:


    $ dsconf import -h localhost -p 1389 \
     /opt/SUNWdsee/ds6/ldif/Example.ldif dc=example,dc=com
    Enter "cn=directory manager" password:  
    New data will override existing data of the suffix
     "dc=example,dc=com".
    Initialization will have to be performed on replicated suffixes. 
    Do you want to continue [y/n] ? y
    
    ## Index buffering enabled with bucket size 16
    ## Beginning import job...
    ## Processing file "/opt/SUNWdsee/ds6/ldif/Example.ldif"
    ## Finished scanning file "/opt/SUNWdsee/ds6/ldif/Example.ldif" (160 entries)
    ## Workers finished; cleaning up...
    ## Workers cleaned up.
    ## Cleaning up producer thread...
    ## Indexing complete.
    ## Starting numsubordinates attribute generation.
     This may take a while, please wait for further activity reports.
    ## Numsubordinates attribute generation complete. Flushing caches...
    ## Closing files...
    ## Import complete. Processed 160 entries in 5 seconds.
     (32.00 entries/sec)
    
    Task completed (slapd exit code: 0).
    $ 
See Also

You can use Directory Service Control Center to perform this task. For more information, see the Directory Service Control Center online help.

ProcedureTo Register the Plug-In

If you have not already done so, build the example plug-in library and activate both plug-in informational logging and the example plug-in.

  1. Build the plug-in.

    Hint Use install-path/examples/Makefile or install-path/examples/Makefile64.

  2. Configure Directory Server to log plug-in informational messages and load the plug-in.

    Hint Use the commands specified in the comments at the outset of the plug-in source file.

  3. Restart Directory Server.


    $ dsadm restart instance-path
    

ProcedureTo Use the Password Check Plug-In

Before You Begin

Populate the suffix dc=example,dc=com with sample data. Also, register the plug-in with Directory Server.

  1. Enforce password quality checking so Directory Server calls your password check plug-in.


    $ dsconf set-server-prop -h localhost -p 1389 \
     pwd-check-enabled:on pwd-strong-check-enabled:off
  2. Enable logging of informational messages.


    $ dsconf set-log-prop -h localhost -p 1389 error level:err-plugins
  3. Prepare an entry that tests your password quality check.


    $ cat quentin.ldif
    dn: uid=qcubbins,ou=People,dc=example,dc=com
    objectclass: top
    objectclass: person
    objectclass: organizationalPerson
    objectclass: inetOrgPerson
    uid: qcubbins
    givenName: Quentin
    sn: Cubbins
    cn: Quentin Cubbins
    mail: quentin.cubbins@example.com
    userPassword: secret12
    
  4. Add the entry to the directory.


    $ ldapmodify -a -D uid=kvaughan,ou=people,dc=example,dc=com \
    -w bribery -h localhost -p 1389 -f quentin.ldif
    
    adding new entry uid=qcubbins,ou=People,dc=example,dc=com
    ldap_add_s: Constraint violation
  5. Check the errors log for further information.


    $ grep secret12 /local/ds/logs/errors
    [16/Feb/2006:18:13:06 +0100] - INFORMATION - 
    Sample password check plug-in - conn=0 op=1 msgId=2 -  
    Invalid password: secret12

    The example log message as shown has been wrapped for readability in the printed version of this document.

Chapter 14 Writing Computed Attribute Plug-Ins

This chapter describes a plug-in that computes an attribute value when that value is requested by a client application.

This chapter covers the following topics:

Computed Attributes and Performance

Unlike most attributes in Directory Server, computed attributes do not have values stored in the Directory Server database. Computed attributes become available when the values are requested. Therefore, the attribute values must be computed at the time of the request, every time the values are requested.

In general, therefore, computed attributes should be quick to process. Computed attributes that depend on large internal searches, or even access to data not contained in the directory, can be expensive to generate. Therefore, such attributes are not ideal candidates for computed attributes.

Instead computed attributes that generate values quickly from the data at hand are more typical. For example, you might use a computed attribute to count or otherwise process the values of other attributes in an entry. This chapter shows a simple, artificial example that uses the API to get the current time when reading an entry.

Writing a Computed Attribute Plug-In

This section demonstrates how to write a plug-in that generates the value of an attribute, currentTime.

Only code excerpts are shown in this chapter. The complete code example can be found where you installed Directory Server, install-path/ds6/examples/computed.c.

Initializing the Computed Attribute Plug-In

Plug-ins that compute attribute values register the functions of type slapi_compute_callback_t to compute attributes with slapi_compute_add_evaluator() during initialization with the server.

#include "slapi-plugin.h"

int
compute_init(Slapi_PBlock * pb)
{
    int rc = 0;                        /* 0 means success             */
    rc |= slapi_pblock_set(            /* Plug-in API version         */
        pb,
        SLAPI_PLUGIN_VERSION,
        SLAPI_PLUGIN_CURRENT_VERSION
    );
    rc |= slapi_pblock_set(            /* Plug-in description         */
        pb,
        SLAPI_PLUGIN_DESCRIPTION,
        (void *) &comp_desc;           /* See the code for comp_desc. */
    );
    rc |= slapi_compute_add_evaluator(compute_attrs);
    return rc;
}

Here, compute_attrs() is the function that computes the attribute value.

Computing an Attribute Value

When Directory Server receives a request for the computed attribute value, in this case currentTime, the server calls the functions that are registered to compute attributes. Directory Server provides your function the context, attribute type, and entry with which to compute the attribute.

Your function must therefore check the attribute type to see whether your function should handle the computation. If your function does not handle the attribute type in question, return -1. If your function can handle the attribute, your function must determine the value or values of the attribute. Your function must then add the computed content to the result that the server returns. The following code excerpt shows the compute_attrs() function generating a current time string for the attribute value.

#include "slapi-plugin.h"

/* Compute an attribute called currentTime                        */
static int
compute_attrs(
    computed_attr_context  * context,
    char                   * type,
    Slapi_Entry            * entry,
    slapi_compute_output_t   outfn
)
{
    /* If the attribute type is not recognized return -1.         */
    int rc = -1;

    if (slapi_attr_type_cmp("currentTime", type, 2) == 0) {
        Slapi_Attr  * time_attr;
        time_t        now;
        char          current_time[30];
        Slapi_Value * time_value;
        int           rc = 0;

        /* Construct the computed attribute.                      */
        time_attr = slapi_attr_new();
        slapi_attr_init(time_attr, type);
        now = time(NULL);
        ctime_r(&now;, current_time, 30);
        current_time[(int)strlen(current_time) - 1] = '\0';
        time_value = slapi_value_new_string(current_time);
        slapi_attr_add_value(time_attr, time_value);
        slapi_value_free(&time_value;);

        /* Add the attribute to the result returned.              */
        rc = (*outfn)(context, time_attr, entry);
        attr_done(time_attr);
        return rc;

    /* Handle other computed attributes...
    } else if (slapi_attr_type_cmp("myCompAttr", type, 2) == 0) {
    */

    }
    return rc;
}

Notice the lines of code that add the attribute to the result that is returned. The output function in this case is of type slapi_compute_output_t.

Notice also, when constructing the computed attribute value, that the compute_attrs() function removes the newline character from the string in the ctime_r() function. This removal prevents the attribute value from appearing base64-encoded when the value is displayed by the ldapsearch command.

Testing the Computed Attribute Plug-In

Before you can test the plug-in with the ldapsearch command, you must register the plug-in with Directory Server.

ProcedureTo Register the Plug-In

If you have not already done so, build the example plug-in library and activate both plug-in informational logging and the example plug-in.

  1. Build the plug-in.

    Hint Use install-path/examples/Makefile or install-path/examples/Makefile64.

  2. Configure Directory Server to log plug-in informational messages and load the plug-in.

    Hint Use the commands specified in the comments at the outset of the plug-in source file.

  3. Restart Directory Server.


    $ dsadm restart instance-path
    

ProcedureTo use the Computed Attribute Plug-In

Before You Begin

Register the computed attribute plug-in with Directory Server.

  1. Run a search on the root DSE.


    $ ldapsearch -h localhost -p 1389 -b "" -s base "(objectclass=*)" currentTime
        version: 1
        dn:
        currentTime: Fri Feb 17 09:30:28 2006