From 389 Directory Server
Contents |
Directory Server Plugins
If you want to extend the capabilities of the Fedora Directory Server, you can write your own server plug-in. A server plug-in is a shared object or library (or a dynamic link library on Windows) containing your own functions.
You can write your own plug-in functions to extend the functionality of the directory server in the following ways:
- You can validate data before the server performs an LDAP operation on the data.
- You can perform some action (that you define) after the server successfully completes an LDAP operation. (For example, you can send mail after an operation successfully completes.)
- You can define extended operations (which are part of the LDAP v3 protocol).
- You can provide alternate matching rules when comparing certain attribute values.
- You can set up the server to use your own type of database for storing data.
There are many different types of plug-ins.
Pre-Operation plug-ins are called after the LDAP operation has been parsed but before any database activity occurs. These can be thought of as filters They are typically used to filter out undesired operations or data. They are also used to implement BIND operation plug-ins which can be used to authenticate users against other systems, such as PAM or a SecurID ACE server. The Directory Server uses this to support SASL, allowing users to authenticate to the Directory with their Kerberos credentials.
Data Interoperability plug-ins are just pre-operation plug-ins with some special extra configuration (in the mapping tree) that allows them to intercept requests for the root DSE as well (so you can do a subtree search with a base of ""). These are used to put an LDAP front-end on external data sources without having to implement the full database plug-in interface.
Post-Operation plug-ins are called after database activity has occurred. These can be thought of as triggers. They are typically used to perform some other operation that depends on the outcome of the original operation (whether positive or negative). For example, removing references to a user (e.g. from groups) after the user has been successfully removed, or sending an email to an administrator if a certain operation with a certain value failed.
Backend Pre and Post Operation plug-ins are similar to those described above, except that they are called inside the database transaction lock and can therefore be used to perform atomic operations.
An Extendop plug-in implements support for an LDAP Extended Operation. Multi Master Replication defines several extended operations and the plug-ins to support them.
A Syntax plug-in implements support for LDAP syntaxes, controlling how values of a certain syntax are indexed and compared. For example, the Telephone Number Syntax plug-in makes sure that "800-555-1212" and "800 555 1212" compare to the same value. All of the LDAP syntax support in the Directory Server is implemented as Syntax plug-ins.
A MatchingRule plug-in provides support for doing locale/charset based comparisons and indexes. There are many languages and charsets supported by default already, but this plug-in type provides the ability to define custom rules.
An AccessControl plug-in provides the implementation of the Access Control features of the Directory Server. The default access control used by the Directory Server is implemented as a plug-in of this type.
A PasswordStorageScheme plug-in allows users to add support for other one way password hashing and encryption algorithms (like SSHA). There are several password storage schemes supported by the Directory Server, all implemented as plug-ins of this type. There is also a ReverPasswordStorageScheme plug-in type which allows the use of reversibly encrypted passwords.
An Object plug-in can register at start-up time to be any and all other types of plug-ins. The Multi Master Replication plug-in is an example of this. This allows the programmer to implement the desired functionality in one place rather than writing and registering several different plug-ins of other types.
A database plug-in implements an interface to a particular type of data store. The Directory Server uses a database plug-in called back-ldbm (no relation to the LDBM database anymore) to implement an interface to Berkeley DB. The Directory Server also has a database plug-in called Chaining which uses one or more other LDAP servers as the data store, and a pseudo, hidden database called DSE that stores the schema and configuration information in LDIF text files.
A plug-in can be disabled. When disabled, the plug-in's configuration information remains in the directory but its function will not be used by the server.
On startup, the directory server loads your library and calls your functions during the course of processing various LDAP requests.
For more details, see the Plug-in Programmers Guide.
An Annotated Pre-Operation Plug-in Example
This example is taken from the file ldap/servers/slapd/test-plugins/testpreop.c
#include <stdio.h> #include <string.h> #include "slapi-plugin.h"
Every plug-in must include slapi-plugin.h - this defines the interface to the server.
static void *my_plugin_identity = NULL;
Every plug-in is assigned an identity by the server plug-in code. The plug-in can retrieve this in the plug-in init function (see below). The plug-in must use this identity in order to call internal LDAP operations (slapi_modify_internal_pb et. al.).
Slapi_PluginDesc preoppdesc = { "test-preop", "My Company", "1.0",
"sample pre-operation plugin" };
The Slapi_PluginDesc is some data that populates the corresponding entries in the plug-in entry. The first field is the plugin name which is also the cn attribute in the plug-in entry which is used as the RDN (e.g. cn=test-preop,cn=plugins,cn=config). Choose something that works well in a DN (e.g. avoid 8 bit characters, commas, equal signs, etc.). The second field identifies the plugin vendor or author. The third field is the plug-in version. The fourth field is a brief description of the plug-in.
/* Pre-operation plug-in function */ int testpreop_bind( Slapi_PBlock *pb )
This is the function that we have registered in testpreop_init() (see below) to be called during the pre-operation phase of each BIND operation.
{
char *dn;
int method;
char *auth;
/* Get the DN that the client is binding as and the method
of authentication used. */
if ( slapi_pblock_get( pb, SLAPI_BIND_TARGET, &dn ) != 0 ||
slapi_pblock_get( pb, SLAPI_BIND_METHOD, &method ) != 0 ) {
slapi_log_error( SLAPI_LOG_PLUGIN,
"testpreop_bind", "Could not get parameters\n" );
return( -1 );
}
pblock stands for Parameter Block. This contains all of the parameters about the operation, connection, etc. See the Plug-in Programmers Guide for full details about what parameters are available for each operation. This is a BIND operation, and two of the parameters are the BIND DN (i.e. who is attempting to bind) and the method. The dn is a pointer into the internal pblock structure - do not free it or otherwise modify it. Use slapi_ch_strdup() to make a copy of it to modify or use the slapi_sdn_* functions. slapi_pblock_get() should return a 0 upon success. slapi_log_error is the mechanism used to write messages to the errors log file. The first argument is the error level (not a severity!). These levels are listed in slapi-plugin.h. In general, use SLAPI_LOG_FATAL for urgent messages - these are always logged. Use SLAPI_LOG_PLUGIN for everything else unless you are writing a special purpose plug-in that would make more sense to use one of the other log levels. The second argument is the subsystem - in this case "testpreop_bind". This is mainly used to help search and categorize related error messages. The third argument is a format string in the style of printf, and the remaining arguments (if any) are treated as vararg arguments. The format string should end in \n unless you just really want your log message to be on the same line as the next message (you probably don't).
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 method of authentication";
break;
}
LDAP_AUTH_NONE, LDAP_AUTH_SIMPLE, and LDAP_AUTH_SASL are defined in ldap-deprecated.h. An SSL client cert auth connection will be LDAP_AUTH_SASL and the cert information will be in the pblock.
/* Log information about the bind operation to the server error log. */ slapi_log_error( SLAPI_LOG_PLUGIN, "testpreop_bind", "Preoperation bind function called.\n" "\tTarget DN: %s\n\tAuthentication method: %s\n", dn, auth );
Here is an example of using slapi_log_error() with additional arguments. This formats the output on separate lines, which is not usually recommended - it just makes parsing the log harder.
return( 0 ); /* allow the operation to continue */
A return code of 0 means that you want the regular bind processing in the server to occur, including sending the result back to the client. A return code of 1 means that the plugin already handled the rest of the processing, including returning the result to the client along with any response controls.
} /* Pre-operation plug-in function */ int testpreop_add( Slapi_PBlock *pb )
This is the function that we have registered in testpreop_init() (see below) to be called during the pre-operation phase of each ADD operation.
{
Slapi_Entry *e;
Slapi_Attr *a;
Slapi_Value *v;
struct berval **bvals;
int i, hint;
char *tmp;
const char *s;
/* Get the entry that is about to be added. */
if ( slapi_pblock_get( pb, SLAPI_ADD_ENTRY, &e ) != 0 ) {
slapi_log_error( SLAPI_LOG_PLUGIN,
"testpreop_add", "Could not get entry\n" );
return( -1 );
}
SLAPI_ADD_ENTRY is the entry that is going to be added to the database. It is ok to modify it here. It is not recommended to free it and/or replace it with another one.
/* Prepend the name "BOB" to the value of the cn attribute
in the entry. */
if ( slapi_entry_attr_find( e, "cn", &a ) == 0 ) {
for ( hint = slapi_attr_first_value( a, &v ); hint != -1;
hint = slapi_attr_next_value( a, hint, &v )) {
s = slapi_value_get_string( v );
All of the slapi_entry, slapi_attr, and slapi_value functions used here return pointers into the internal data structures - do not free them or modify them directly.
tmp = slapi_ch_malloc( 5 + strlen( s )); strcpy( tmp, "BOB " ); strcat( tmp + 4, s ); slapi_value_set_string( v, tmp );
slapi_ch_malloc() works just like system malloc(). You must use the slapi_ch malloc and free functions when passing memory to other slapi functions. slapi_value_set_string will free the current value (if any) and set the value to a copy of the passed in string.
slapi_ch_free_string( &tmp );
We have to free our temporary string to avoid a memory leak. slapi_ch_free() and slapi_ch_free_string() accept a pointer to the string to free. This is because it sets its argument to NULL after the free to avoid freeing freed memory. It also checks for NULL so it is safe to pass in a NULL pointer. These added features protect against hard to find memory errors.
} } return( 0 ); /* allow the operation to continue */ } /* Pre-operation plug-in function */ int testpreop_abandon( Slapi_PBlock *pb )
This is the function that we have registered in testpreop_init() (see below) to be called during the pre-operation phase of each ABANDON operation.
{
int msgid;
/* Get the LDAP message ID of the abandon target */
if ( slapi_pblock_get( pb, SLAPI_ABANDON_MSGID, &msgid ) != 0 ) {
slapi_log_error( SLAPI_LOG_PLUGIN,
"testpreop_abandon", "Could not get parameters\n" );
return( -1 );
}
msgid is the LDAP message ID.
/* Log information about the abandon operation to the server error log. */ slapi_log_error( SLAPI_LOG_PLUGIN, "testpreop_bind", "Preoperation abandon function called.\n" "\tTarget MsgID: %d\n", msgid ); return( 0 ); /* allow the operation to continue */ } static void get_plugin_config_dn_and_entry( char *msg, Slapi_PBlock *pb )
This is a helper function called from the plug-in start function testpreop_start() below.
{
char *dn = NULL;
Slapi_Entry *e = NULL;
int loglevel = SLAPI_LOG_PLUGIN;
if ( slapi_pblock_get( pb, SLAPI_TARGET_DN, &dn ) != 0 || dn == NULL ) {
slapi_log_error( loglevel, msg, "failed to get plugin config DN\n" );
} else {
slapi_log_error( loglevel, msg, "this plugin's config DN is \"%s\"\n",
dn );
}
if ( slapi_pblock_get( pb, SLAPI_ADD_ENTRY, &e ) != 0 || e == NULL ) {
slapi_log_error( loglevel, msg, "failed to get plugin config entry\n" );
} else {
char *ldif;
The plug-in start function has access to the DN and the entry of the plug-in. The plug-in entry is a very useful place to store the plug-in configuration information. For example, if this plug-in entry has a DN of cn=test-preop,cn=plugins,cn=config, this DN and entry will be available here. Most non-trivial plug-ins will have some sort of static structure which represents the configuration parameters of the plug-in. The values can be derived from the attributes and values in the plug-in. An objectclass and attributes can be created for the plug-in configuration and added to the schema. DSE callbacks can be registered so that the plug-in can be notified when the values change, so that the plug-in can be dynamically configured during operation. In this case, use mutexes to protect your configuration parameters when modifying and accessing their values. Remember, this is a multi-threaded server, and your plug-in may be called many times simultaneously.
ldif = slapi_entry2str_with_options( e, NULL, 0 );
This converts the entry to a string in LDIF format. The memory returned is allocated and must be freed with slapi_ch_free_string().
slapi_log_error( loglevel, msg, "this plugin's config entry is \"\n%s\"\n", ldif ); slapi_ch_free_string( &ldif );
Frees the allocated string.
} } static int testpreop_start( Slapi_PBlock *pb )
This function is called when the server is initialized and ready to start processing operations, but before the first operation has been received. The server is still in "single threaded" mode at this point, so it is generally safe to do things which are not thread safe, such as initializing static variables and the like. The start function is different from the init function (below) which is called when the plugin shared object is loaded for the first time (usually at server start up).
{
get_plugin_config_dn_and_entry( "testpreop_start", pb );
return( 0 );
}
/* Initialization function */
#ifdef _WIN32
__declspec(dllexport)
#endif
int
testpreop_init( Slapi_PBlock *pb )
This function is called when the plug-in shared object is first loaded into memory, usually at server start up time. This function name must be specified in the plug-in configuration entry under cn=plugins,cn=config. This function should not do very much, mostly just set the operation specific callback functions. The rest of the configuration should be done in the start function when the plug-in has access to the plug-in entry (which should have all of the plug-in configuration information).
{
slapi_pblock_get( pb, SLAPI_PLUGIN_IDENTITY, &my_plugin_identity);
PR_ASSERT (my_plugin_identity);
Get the identity of this plug-in (as assigned by the plug-in system) and make sure it is not NULL.
/* Register the two pre-operation plug-in functions, and specify the server plug-in version. */ if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 || slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&preoppdesc ) != 0 ||
These two things are always required.
slapi_pblock_set( pb, SLAPI_PLUGIN_START_FN, (void *) testpreop_start ) != 0 ||
The plug-in should always have a start function. This is where the plug-in will normally do its configuration.
slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_BIND_FN,
(void *) testpreop_bind ) != 0 ||
slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_ADD_FN,
(void *) testpreop_add ) != 0 ||
slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_ABANDON_FN,
(void *) testpreop_abandon ) != 0 ) {
This plug-in only registers interest in pre BIND, ADD, and ABANDON operations. You can register callbacks for one or all or any combination of LDAP operations. Other types of plug-ins (post op, extended op, etc.) have different callbacks that can be set here.
slapi_log_error( SLAPI_LOG_PLUGIN, "testpreop_init", "Failed to set version and function\n" ); return( -1 ); } return( 0 ); }
An Annotated LDAP Extended Operation Plug-in Example
This example is taken from the file ldap/servers/slapd/test-plugins/testextendedop.c. See RFC 2251 for more information about LDAP extended operations.
#include <stdio.h> #include <string.h> #include "slapi-plugin.h"
Always need slapi-plugin.h.
/* OID of the extended operation handled by this plug-in */ #define MY_OID "1.2.3.4"
All extended operations have an OID. See the LDAP RFCs for more information about extended operations and their OID assignments.
Slapi_PluginDesc expdesc = { "test-extendedop", "Fedora", "0.5",
"sample extended operation plugin" };
Our plug-in description information. Required.
/* Extended operation plug-in */ int testexop_babs( Slapi_PBlock *pb )
This is the function that we have registered below (in the init function) to handle our extended operation.
{
char *oid;
struct berval *bval;
char *retval, *msg;
struct berval retbval;
/* Get the OID and the value included in the request */
if ( slapi_pblock_get( pb, SLAPI_EXT_OP_REQ_OID, &oid ) != 0 ||
slapi_pblock_get( pb, SLAPI_EXT_OP_REQ_VALUE, &bval ) != 0 ) {
msg = "Could not get OID and value from request.";
slapi_log_error( SLAPI_LOG_PLUGIN, "testexop_babs", "%s\n",
msg );
slapi_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL,
msg, 0, NULL );
return( SLAPI_PLUGIN_EXTENDED_SENT_RESULT );
} else {
slapi_log_error( SLAPI_LOG_PLUGIN, "testexop_babs",
"Received extended operation request with OID %s\n",
oid );
slapi_log_error( SLAPI_LOG_PLUGIN, "testexop_babs",
"Value from client: %s\n", bval->bv_val );
}
Since this is an extended operation, the OID sent by the client and the extended op data will be available in the pblock. The oid and bval returned are actual pointers into the internal data structures of the pblock and should not be freed or modified. Use slapi_ch_strdup() to make a copy of oid, and use ber_bvdup() to make a copy of bval, if needed. The OID should be exactly the same as the one we registered in the init function below. This plug-in performs some data validation, and actually returns the LDAP result back to the client using slapi_send_ldap_result(). The return code SLAPI_PLUGIN_EXTENDED_SENT_RESULT tells the server that the result was already handled. NOTE: It is a bad idea to print the bval->bv_val using a %s printf specifier since it may not be a NULL terminated string. Use the field width format specifier with the bv_len as the value. You never know what a malicious client might try to do (can you say buffer overflow or DoS?).
/* Set up the value that you want returned to the client. In this case, it's just the value sent from the client, preceded by the string "Value from client: " */ msg = "Value from client: "; retval = ( char * )slapi_ch_malloc( bval->bv_len + strlen( msg ) + 1 ); sprintf( retval, "%s%s", msg, bval->bv_val );
Again, this is a bad idea in production code. Use a length modifier when printing the bval->bv_val and make sure the retval string is properly null terminated.
retbval.bv_val = retval;
retbval.bv_len = strlen( retbval.bv_val );
/* Prepare to return the OID and value back to the client.
Note that if you want, you can return a different OID to
the client (for example, if you want to use the OID as
an indicator of something). */
if ( slapi_pblock_set( pb, SLAPI_EXT_OP_RET_OID, "5.6.7.8" ) != 0 ||
slapi_pblock_set( pb, SLAPI_EXT_OP_RET_VALUE, &retbval ) != 0 ) {
slapi_ch_free( ( void ** ) &retval );
msg = "Could not set return values";
slapi_log_error( SLAPI_LOG_PLUGIN, "testexop_babs", "%s\n",
msg );
slapi_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL,
msg, 0, NULL );
return( SLAPI_PLUGIN_EXTENDED_SENT_RESULT );
}
/* Send the response (containing the OID and value you set)
back to the client. */
slapi_send_ldap_result( pb, LDAP_SUCCESS, NULL,
"operation babs successful!", 0, NULL );
slapi_log_error( SLAPI_LOG_PLUGIN, "testexop_babs",
"OID sent to client: %s\n", "5.6.7.8" );
slapi_log_error( SLAPI_LOG_PLUGIN, "testexop_babs",
"Value sent to client: %s\n", retval );
/* Free any memory allocated by this plug-in. */
slapi_ch_free( ( void ** ) &retval );
/* Let front end know we sent the result */
return( SLAPI_PLUGIN_EXTENDED_SENT_RESULT );
Could also return 0 here to let the server handle the result.
} /* Initialization function */ #ifdef _WIN32 __declspec(dllexport) #endif int testexop_init( Slapi_PBlock *pb )
This function is called when the plug-in shared object is first loaded into memory, usually at server start up time. This function name must be specified in the plug-in configuration entry under cn=plugins,cn=config. This function should not do very much, mostly just set the operation specific callback functions. The rest of the configuration should be done in the start function when the plug-in has access to the plug-in entry (which should have all of the plug-in configuration information). This plug-in has no start function, but a non-trivial extended operation handling plug-in would.
{
char **oidlist, **namelist;
oidlist = (char **) slapi_ch_malloc( 2 * sizeof( char * ) );
oidlist[0] = MY_OID;
oidlist[1] = NULL;
namelist = (char **) slapi_ch_malloc( 2 * sizeof( char * ) );
namelist[0] = "test extended op";
namelist[1] = NULL;
/* Register the plug-in function as an extended operation
plug-in function that handles the operation identified by
OID 1.2.3.4. Also specify the version of the server
plug-in */
if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION,
SLAPI_PLUGIN_VERSION_01 ) != 0 ||
slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION,
(void *)&expdesc ) != 0 ||
These two are always required.
slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_FN, (void *) testexop_babs ) != 0 ||
This is required because this is an extended operation plug-in. We could also have a start function.
slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_OIDLIST, oidlist ) ||
slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_NAMELIST, namelist ) != 0 ) {
These are required. The oidlist is the NULL terminated array of strings, where each string is the OID handled by this plug-in. The namelist is a NULL terminated array of user friendly names corresponding to the OIDs in the OIDLIST.
slapi_log_error( SLAPI_LOG_PLUGIN, "testexop_init", "Failed to set plug-in version, function, and OID.\n" ); return( -1 ); } return( 0 ); }
An Annotated Internal Modify Operation
The following is an example of how to perform a modify operation from a plug-in. You might want to do this, for exmaple, after a BIND operation, to record the last login time in the user's entry.
In the following function, the dn is the DN of the entry to modify, the attrname is the name of the attribute to modify, and the newvalue is the value to REPLACE it with. REPLACE will add the attribute if it doesn't already exist, and will delete any old values. Note that this does not bypass schema checking, but it does bypass access control. Use slapi_access_allowed() if you need to check access.
You can only use this if you are sure that newvalue is a properly NULL terminated string. Otherwise, pass in the length explicitly, or use a struct berval.
static int
my_internal_modify(const char *dn, const char *attrname, const char *newvalue)
{
Slapi_PBlock *pb = NULL; /* every operation needs a pblock */
Slapi_Mods *smods = NULL; /* to hold our modifies */
int rc = 0; /* the result code of the operation */
pb = slapi_pblock_new(); /* must allocate and init the pblock */
smods = slapi_mods_new(); /* must allocate and init the smods */
This allocates memory that we need to free - see below.
slapi_mods_add_string(smods, LDAP_MOD_REPLACE, attrname, newvalue);
You can only use slapi_mods_add_string if you are absolutely positive that newvalue is a properly NULL terminated string. If not, use slapi_mods_add() and explicitly pass in the length, or use a struct berval * with slapi_mods_add_modbvps(). The slapi_mods_add*() functions make copies of all strings passed in, and slapi_mods_free() will clean up all of those.
slapi_modify_internal_set_pb(pb, dn, slapi_mods_get_ldapmods_byref(smods),
NULL /* ldap controls */, NULL /* uniqueid */,
my_plugin_identity /* my plugin identity */, 0 /* operation flags */);
This sets our modify arguments into the pb. slapi_mods_get_ldapmods_byref() passes the LDAPMod** pointer. The first NULL is an LDAPControl** list of any controls we want to apply. The second NULL is the uniqueid of the entry, which is primarily used in replication code. Every plug-in has an identity which is used internally to keep track of the plug-in. This is obtained in the plug-in init or start function and is usually kept in a static void* (see above). The operation flags are any special modifiers we want to apply to this operation.
slapi_modify_internal_pb(pb);
This is the actual call to perform the modifications.
slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
This is the result code of our operation. If successful, rc will be 0 (LDAP_SUCCESS). Otherwise, it will either be an LDAP result code that would normally be returned by an LDAP modify operation (e.g. LDAP_CONSTRAINT_VIOLATION, LDAP_OBJECT_CLASS_VIOLATION, LDAP_UNWILLING_TO_PERFORM, etc.) or a -1 which means some internal error occurred.
if (LDAP_SUCCESS != rc) {
char ebuf[BUFSIZ];
slapi_log_error(SLAPI_LOG_FATAL, "my_plugin_name",
"Error [%d] for internal modify op for entry [%s] attribute [%s] value [%s]\n",
rc, escape_string(dn, ebuf), attrname, newvalue);
}
Log an error message. Note: do not log newvalue if it could contain sensitive information. The escape_string() is to make sure the DN value is readable by humans and parseable by scripts.
slapi_mods_free(&smods);
We pass in the address because slapi_mods_free will also set the pointer to NULL to avoid a double free. In simple code like this, it's no big deal. But if you have dozens of lines of code with branching and looping, it can be a real life saver.
slapi_pblock_destroy(pb);
Have to clean up after ourselves.
return rc;
}
Here is the function in its entirety, un-annotated:
static int
my_internal_modify(const char *dn, const char *attrname, const char *newvalue)
{
Slapi_PBlock *pb = NULL; /* every operation needs a pblock */
Slapi_Mods *smods = NULL; /* to hold our modifies */
int rc = 0; /* the result code of the operation */
pb = slapi_pblock_new(); /* must allocate and init the pblock */
smods = slapi_mods_new(); /* must allocate and init the smods */
slapi_mods_add_string(smods, LDAP_MOD_REPLACE, attrname, newvalue);
slapi_modify_internal_set_pb(pb, dn, slapi_mods_get_ldapmods_byref(smods),
NULL /* ldap controls */, NULL /* uniqueid */,
my_plugin_identity /* my plugin identity */, 0 /* operation flags */);
slapi_modify_internal_pb(pb);
slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
if (LDAP_SUCCESS != rc) {
char ebuf[BUFSIZ];
slapi_log_error(SLAPI_LOG_FATAL, "my_plugin_name",
"Error [%d] for internal modify op for entry [%s] attribute [%s] value [%s]\n",
rc, escape_string(dn, ebuf), attrname, newvalue);
}
slapi_mods_free(&smods);
slapi_pblock_destroy(pb);
return rc;
}
