openldap/servers/slapd/modify.c
Kurt Zeilenga 82fad7d0c8 First stable an implementing latest namedref specification.
Includes rewriting of URLs where the DN of the referral object
and the DN of the ref attribute attribute are not the same.
Also, always returns explicit DN and scope.
Currently, back-ldbm only.  Needs to be ported to back-bdb.
2001-10-26 02:05:14 +00:00

594 lines
15 KiB
C

/* $OpenLDAP$ */
/*
* Copyright 1998-2000 The OpenLDAP Foundation, All Rights Reserved.
* COPYING RESTRICTIONS APPLY, see COPYRIGHT file
*/
/*
* Copyright (c) 1995 Regents of the University of Michigan.
* All rights reserved.
*
* Redistribution and use in source and binary forms are permitted
* provided that this notice is preserved and that due credit is given
* to the University of Michigan at Ann Arbor. The name of the University
* may not be used to endorse or promote products derived from this
* software without specific prior written permission. This software
* is provided ``as is'' without express or implied warranty.
*/
#include "portable.h"
#include <stdio.h>
#include <ac/socket.h>
#include <ac/string.h>
#include <ac/time.h>
#include "ldap_pvt.h"
#include "slap.h"
int
do_modify(
Connection *conn,
Operation *op )
{
char *dn, *ndn = NULL;
char *last;
ber_tag_t tag;
ber_len_t len;
LDAPModList *modlist = NULL;
LDAPModList **modtail = &modlist;
#ifdef LDAP_DEBUG
LDAPModList *tmp;
#endif
Modifications *mods = NULL;
Backend *be;
int rc;
const char *text;
int manageDSAit;
#ifdef NEW_LOGGING
LDAP_LOG(( "operation", LDAP_LEVEL_ENTRY,
"do_modify: enter\n" ));
#else
Debug( LDAP_DEBUG_TRACE, "do_modify\n", 0, 0, 0 );
#endif
/*
* Parse the modify request. It looks like this:
*
* ModifyRequest := [APPLICATION 6] SEQUENCE {
* name DistinguishedName,
* mods SEQUENCE OF SEQUENCE {
* operation ENUMERATED {
* add (0),
* delete (1),
* replace (2)
* },
* modification SEQUENCE {
* type AttributeType,
* values SET OF AttributeValue
* }
* }
* }
*/
if ( ber_scanf( op->o_ber, "{a" /*}*/, &dn ) == LBER_ERROR ) {
#ifdef NEW_LOGGING
LDAP_LOG(( "operation", LDAP_LEVEL_ERR,
"do_modify: ber_scanf failed\n" ));
#else
Debug( LDAP_DEBUG_ANY, "do_modify: ber_scanf failed\n", 0, 0, 0 );
#endif
send_ldap_disconnect( conn, op,
LDAP_PROTOCOL_ERROR, "decoding error" );
return SLAPD_DISCONNECT;
}
#ifdef NEW_LOGGING
LDAP_LOG(( "operation", LDAP_LEVEL_ARGS,
"do_modify: dn (%s)\n", dn ));
#else
Debug( LDAP_DEBUG_ARGS, "do_modify: dn (%s)\n", dn, 0, 0 );
#endif
/* collect modifications & save for later */
for ( tag = ber_first_element( op->o_ber, &len, &last );
tag != LBER_DEFAULT;
tag = ber_next_element( op->o_ber, &len, last ) )
{
ber_int_t mop;
(*modtail) = (LDAPModList *) ch_calloc( 1, sizeof(LDAPModList) );
if ( ber_scanf( op->o_ber, "{i{a[V]}}", &mop,
&(*modtail)->ml_type, &(*modtail)->ml_bvalues )
== LBER_ERROR )
{
send_ldap_disconnect( conn, op,
LDAP_PROTOCOL_ERROR, "decoding modlist error" );
rc = SLAPD_DISCONNECT;
goto cleanup;
}
switch( mop ) {
case LDAP_MOD_ADD:
if ( (*modtail)->ml_bvalues == NULL ) {
#ifdef NEW_LOGGING
LDAP_LOG(( "operation", LDAP_LEVEL_ERR,
"do_modify: modify/add operation (%ld) requires values\n",
(long)mop ));
#else
Debug( LDAP_DEBUG_ANY,
"do_modify: modify/add operation (%ld) requires values\n",
(long) mop, 0, 0 );
#endif
send_ldap_result( conn, op, LDAP_PROTOCOL_ERROR,
NULL, "modify/add operation requires values",
NULL, NULL );
rc = LDAP_PROTOCOL_ERROR;
goto cleanup;
}
/* fall through */
case LDAP_MOD_DELETE:
case LDAP_MOD_REPLACE:
break;
default: {
#ifdef NEW_LOGGING
LDAP_LOG(( "operation", LDAP_LEVEL_ERR,
"do_modify: invalid modify operation (%ld)\n",
(long)mop ));
#else
Debug( LDAP_DEBUG_ANY,
"do_modify: invalid modify operation (%ld)\n",
(long) mop, 0, 0 );
#endif
send_ldap_result( conn, op, LDAP_PROTOCOL_ERROR,
NULL, "unrecognized modify operation", NULL, NULL );
rc = LDAP_PROTOCOL_ERROR;
goto cleanup;
}
}
(*modtail)->ml_op = mop;
modtail = &(*modtail)->ml_next;
}
*modtail = NULL;
if( (rc = get_ctrls( conn, op, 1 )) != LDAP_SUCCESS ) {
#ifdef NEW_LOGGING
LDAP_LOG(( "operation", LDAP_LEVEL_ERR,
"do_modify: get_ctrls failed\n" ));
#else
Debug( LDAP_DEBUG_ANY, "do_modify: get_ctrls failed\n", 0, 0, 0 );
#endif
goto cleanup;
}
ndn = ch_strdup( dn );
if( dn_normalize( ndn ) == NULL ) {
#ifdef NEW_LOGGING
LDAP_LOG(( "operation", LDAP_LEVEL_ERR,
"do_modify: invalid dn (%s)\n", dn ));
#else
Debug( LDAP_DEBUG_ANY, "do_modify: invalid dn (%s)\n", dn, 0, 0 );
#endif
send_ldap_result( conn, op, rc = LDAP_INVALID_DN_SYNTAX, NULL,
"invalid DN", NULL, NULL );
goto cleanup;
}
if( *ndn == '\0' ) {
#ifdef NEW_LOGGING
LDAP_LOG(( "operation", LDAP_LEVEL_ERR,
"do_modify: attempt to modify root DSE.\n" ));
#else
Debug( LDAP_DEBUG_ANY, "do_modify: root dse!\n", 0, 0, 0 );
#endif
send_ldap_result( conn, op, rc = LDAP_UNWILLING_TO_PERFORM,
NULL, "modify upon the root DSE not supported", NULL, NULL );
goto cleanup;
#if defined( SLAPD_SCHEMA_DN )
} else if ( strcasecmp( ndn, SLAPD_SCHEMA_DN ) == 0 ) {
#ifdef NEW_LOGGING
LDAP_LOG(( "operation", LDAP_LEVEL_ERR,
"do_modify: attempt to modify subschema subentry.\n" ));
#else
Debug( LDAP_DEBUG_ANY, "do_modify: subschema subentry!\n", 0, 0, 0 );
#endif
send_ldap_result( conn, op, rc = LDAP_UNWILLING_TO_PERFORM,
NULL, "modification of subschema subentry not supported",
NULL, NULL );
goto cleanup;
#endif
}
#ifdef LDAP_DEBUG
#ifdef NEW_LOGGING
LDAP_LOG(( "operation", LDAP_LEVEL_DETAIL1,
"do_modify: modifications:\n" ));
#else
Debug( LDAP_DEBUG_ARGS, "modifications:\n", 0, 0, 0 );
#endif
for ( tmp = modlist; tmp != NULL; tmp = tmp->ml_next ) {
#ifdef NEW_LOGGING
LDAP_LOG(( "operation", LDAP_LEVEL_DETAIL1,
"\t%s: %s\n", tmp->ml_op == LDAP_MOD_ADD ?
"add" : (tmp->ml_op == LDAP_MOD_DELETE ?
"delete" : "replace"), tmp->ml_type ));
#else
Debug( LDAP_DEBUG_ARGS, "\t%s: %s\n",
tmp->ml_op == LDAP_MOD_ADD
? "add" : (tmp->ml_op == LDAP_MOD_DELETE
? "delete" : "replace"), tmp->ml_type, 0 );
#endif
}
#endif
Statslog( LDAP_DEBUG_STATS, "conn=%ld op=%d MOD dn=\"%s\"\n",
op->o_connid, op->o_opid, dn, 0, 0 );
manageDSAit = get_manageDSAit( op );
/*
* We could be serving multiple database backends. Select the
* appropriate one, or send a referral to our "referral server"
* if we don't hold it.
*/
if ( (be = select_backend( ndn, manageDSAit )) == NULL ) {
struct berval **ref = referral_rewrite( default_referral,
NULL, dn, LDAP_SCOPE_DEFAULT );
send_ldap_result( conn, op, rc = LDAP_REFERRAL,
NULL, NULL, ref ? ref : default_referral, NULL );
ber_bvecfree( ref );
goto cleanup;
}
/* check restrictions */
rc = backend_check_restrictions( be, conn, op, NULL, &text ) ;
if( rc != LDAP_SUCCESS ) {
send_ldap_result( conn, op, rc,
NULL, text, NULL, NULL );
goto cleanup;
}
/* check for referrals */
rc = backend_check_referrals( be, conn, op, dn, ndn );
if ( rc != LDAP_SUCCESS ) {
goto cleanup;
}
/* deref suffix alias if appropriate */
ndn = suffix_alias( be, ndn );
/*
* do the modify if 1 && (2 || 3)
* 1) there is a modify function implemented in this backend;
* 2) this backend is master for what it holds;
* 3) it's a replica and the dn supplied is the update_ndn.
*/
if ( be->be_modify ) {
/* do the update here */
int repl_user = (be->be_update_ndn != NULL &&
strcmp( be->be_update_ndn, op->o_ndn ) == 0);
#ifndef SLAPD_MULTIMASTER
/* Multimaster slapd does not have to check for replicator dn
* because it accepts each modify request
*/
if ( be->be_update_ndn == NULL || repl_user )
#endif
{
int update = be->be_update_ndn != NULL;
const char *text;
char textbuf[SLAP_TEXT_BUFLEN];
size_t textlen = sizeof textbuf;
rc = slap_modlist2mods( modlist, update, &mods, &text,
textbuf, textlen );
if( rc != LDAP_SUCCESS ) {
send_ldap_result( conn, op, rc,
NULL, text, NULL, NULL );
goto cleanup;
}
if ( (be->be_lastmod == ON || (be->be_lastmod == UNDEFINED &&
global_lastmod == ON)) && !repl_user )
{
Modifications **modstail;
for( modstail = &mods;
*modstail != NULL;
modstail = &(*modstail)->sml_next )
{
/* empty */
}
rc = slap_mods_opattrs( op, modstail, &text );
if( rc != LDAP_SUCCESS ) {
send_ldap_result( conn, op, rc,
NULL, text,
NULL, NULL );
goto cleanup;
}
}
if ( (*be->be_modify)( be, conn, op, dn, ndn, mods ) == 0
#ifdef SLAPD_MULTIMASTER
&& !repl_user
#endif
) {
/* but we log only the ones not from a replicator user */
replog( be, op, dn, mods );
}
#ifndef SLAPD_MULTIMASTER
/* send a referral */
} else {
struct berval **defref = be->be_update_refs
? be->be_update_refs : default_referral;
struct berval **ref = referral_rewrite( defref,
NULL, dn, LDAP_SCOPE_DEFAULT );
send_ldap_result( conn, op, rc = LDAP_REFERRAL, NULL, NULL,
ref ? ref : defref, NULL );
ber_bvecfree( ref );
#endif
}
} else {
send_ldap_result( conn, op, rc = LDAP_UNWILLING_TO_PERFORM,
NULL, "operation not supported within namingContext", NULL, NULL );
}
cleanup:
free( dn );
if( ndn != NULL ) free( ndn );
if ( modlist != NULL )
slap_modlist_free( modlist );
if ( mods != NULL )
slap_mods_free( mods );
return rc;
}
/*
* convert a raw list of modifications to internal format
* Do basic attribute type checking and syntax validation.
*/
int slap_modlist2mods(
LDAPModList *ml,
int update,
Modifications **mods,
const char **text,
char *textbuf,
size_t textlen )
{
int rc;
Modifications **modtail = mods;
for( ; ml != NULL; ml = ml->ml_next ) {
Modifications *mod;
AttributeDescription *ad = NULL;
mod = (Modifications *)
ch_calloc( 1, sizeof(Modifications) );
/* copy the op */
mod->sml_op = ml->ml_op;
/* convert to attribute description */
rc = slap_str2ad( ml->ml_type, &mod->sml_desc, text );
if( rc != LDAP_SUCCESS ) {
slap_mods_free( mod );
snprintf( textbuf, textlen, "%s: %s",
ml->ml_type, *text );
*text = textbuf;
return rc;
}
ad = mod->sml_desc;
if( slap_syntax_is_binary( ad->ad_type->sat_syntax )
&& !slap_ad_is_binary( ad ))
{
/* attribute requires binary transfer */
slap_mods_free( mod );
snprintf( textbuf, textlen,
"%s: requires ;binary transfer",
ml->ml_type );
*text = textbuf;
return LDAP_UNDEFINED_TYPE;
}
if( !slap_syntax_is_binary( ad->ad_type->sat_syntax )
&& slap_ad_is_binary( ad ))
{
/* attribute requires binary transfer */
slap_mods_free( mod );
snprintf( textbuf, textlen,
"%s: disallows ;binary transfer",
ml->ml_type );
*text = textbuf;
return LDAP_UNDEFINED_TYPE;
}
if (!update && is_at_no_user_mod( ad->ad_type )) {
/* user modification disallowed */
slap_mods_free( mod );
snprintf( textbuf, textlen,
"%s: no user modification allowed",
ml->ml_type );
*text = textbuf;
return LDAP_CONSTRAINT_VIOLATION;
}
if ( is_at_obsolete( ad->ad_type ) &&
( mod->sml_op == LDAP_MOD_ADD || ml->ml_bvalues != NULL ) )
{
/*
* attribute is obsolete,
* only allow replace/delete with no values
*/
slap_mods_free( mod );
snprintf( textbuf, textlen,
"%s: attribute is obsolete",
ml->ml_type );
*text = textbuf;
return LDAP_CONSTRAINT_VIOLATION;
}
/*
* check values
*/
if( ml->ml_bvalues != NULL ) {
ber_len_t nvals;
slap_syntax_validate_func *validate =
ad->ad_type->sat_syntax->ssyn_validate;
if( !validate ) {
slap_mods_free( mod );
*text = "no validator for syntax";
snprintf( textbuf, textlen,
"%s: no validator for syntax %s",
ml->ml_type,
ad->ad_type->sat_syntax->ssyn_oid );
*text = textbuf;
return LDAP_INVALID_SYNTAX;
}
/*
* check that each value is valid per syntax
*/
for( nvals = 0; ml->ml_bvalues[nvals]; nvals++ ) {
rc = validate( ad->ad_type->sat_syntax, ml->ml_bvalues[nvals] );
if( rc != 0 ) {
slap_mods_free( mod );
snprintf( textbuf, textlen,
"%s: value #%ld invalid per syntax",
ml->ml_type, (long) nvals );
*text = textbuf;
return LDAP_INVALID_SYNTAX;
}
}
/*
* a rough single value check... an additional check is needed
* to catch add of single value to existing single valued attribute
*/
if( ( mod->sml_op == LDAP_MOD_ADD || mod->sml_op == LDAP_MOD_REPLACE )
&& nvals > 1 && is_at_single_value( ad->ad_type ))
{
slap_mods_free( mod );
snprintf( textbuf, textlen,
"%s: multiple value provided",
ml->ml_type );
*text = textbuf;
return LDAP_CONSTRAINT_VIOLATION;
}
}
mod->sml_bvalues = ml->ml_bvalues;
ml->ml_values = NULL;
*modtail = mod;
modtail = &mod->sml_next;
}
return LDAP_SUCCESS;
}
int slap_mods_opattrs(
Operation *op,
Modifications **modtail,
const char **text )
{
struct berval name, timestamp;
time_t now = slap_get_time();
char timebuf[22];
struct tm *ltm;
Modifications *mod;
int mop = op->o_tag == LDAP_REQ_ADD
? LDAP_MOD_ADD : LDAP_MOD_REPLACE;
assert( modtail != NULL );
assert( *modtail == NULL );
ldap_pvt_thread_mutex_lock( &gmtime_mutex );
ltm = gmtime( &now );
strftime( timebuf, sizeof(timebuf), "%Y%m%d%H%M%SZ", ltm );
ldap_pvt_thread_mutex_unlock( &gmtime_mutex );
timestamp.bv_val = timebuf;
timestamp.bv_len = strlen(timebuf);
if( op->o_dn == NULL || op->o_dn[0] == '\0' ) {
name.bv_val = SLAPD_ANONYMOUS;
name.bv_len = sizeof(SLAPD_ANONYMOUS)-1;
} else {
name.bv_val = op->o_dn;
name.bv_len = strlen( op->o_dn );
}
if( op->o_tag == LDAP_REQ_ADD ) {
mod = (Modifications *) ch_calloc( 1, sizeof( Modifications ) );
mod->sml_op = mop;
mod->sml_desc = slap_schema.si_ad_creatorsName;
mod->sml_bvalues = (struct berval **) malloc( 2 * sizeof( struct berval * ) );
mod->sml_bvalues[0] = ber_bvdup( &name );
mod->sml_bvalues[1] = NULL;
*modtail = mod;
modtail = &mod->sml_next;
mod = (Modifications *) ch_calloc( 1, sizeof( Modifications ) );
mod->sml_op = mop;
mod->sml_desc = slap_schema.si_ad_createTimestamp;
mod->sml_bvalues = (struct berval **) malloc( 2 * sizeof( struct berval * ) );
mod->sml_bvalues[0] = ber_bvdup( &timestamp );
mod->sml_bvalues[1] = NULL;
*modtail = mod;
modtail = &mod->sml_next;
}
mod = (Modifications *) ch_calloc( 1, sizeof( Modifications ) );
mod->sml_op = mop;
mod->sml_desc = slap_schema.si_ad_modifiersName;
mod->sml_bvalues = (struct berval **) malloc( 2 * sizeof( struct berval * ) );
mod->sml_bvalues[0] = ber_bvdup( &name );
mod->sml_bvalues[1] = NULL;
*modtail = mod;
modtail = &mod->sml_next;
mod = (Modifications *) ch_calloc( 1, sizeof( Modifications ) );
mod->sml_op = mop;
mod->sml_desc = slap_schema.si_ad_modifyTimestamp;
mod->sml_bvalues = (struct berval **) malloc( 2 * sizeof( struct berval * ) );
mod->sml_bvalues[0] = ber_bvdup( &timestamp );
mod->sml_bvalues[1] = NULL;
*modtail = mod;
modtail = &mod->sml_next;
return LDAP_SUCCESS;
}