diff --git a/servers/slapd/acl.c b/servers/slapd/acl.c
index 99bb8364e73064d9a6e7f87be03e046aa062661e..ef85ad2614e8ab6617bf1f04bdcdf6a1dc4b502b 100644
--- a/servers/slapd/acl.c
+++ b/servers/slapd/acl.c
@@ -1990,8 +1990,15 @@ acl_check_modlist(
 			/* fall thru to check value to add */
 
 		case LDAP_MOD_ADD:
+		case SLAP_MOD_ADD_IF_NOT_PRESENT:
 			assert( mlist->sml_values != NULL );
 
+			if ( mlist->sml_op == SLAP_MOD_ADD_IF_NOT_PRESENT
+				&& attr_find( e->e_attrs, mlist->sml_desc ) )
+			{
+				break;
+			}
+
 			for ( bv = mlist->sml_nvalues
 					? mlist->sml_nvalues : mlist->sml_values;
 				bv->bv_val != NULL; bv++ )
@@ -2008,6 +2015,7 @@ acl_check_modlist(
 			break;
 
 		case LDAP_MOD_DELETE:
+		case SLAP_MOD_SOFTDEL:
 			if ( mlist->sml_values == NULL ) {
 				if ( ! access_allowed( op, e,
 					mlist->sml_desc, NULL,
diff --git a/servers/slapd/back-bdb/modify.c b/servers/slapd/back-bdb/modify.c
index c5c5e6a9b81c7ddd16ad18eeb119b2b71d624182..896c06fb390f765b4e6c7bb96b53d90a543e551e 100644
--- a/servers/slapd/back-bdb/modify.c
+++ b/servers/slapd/back-bdb/modify.c
@@ -215,6 +215,56 @@ int bdb_modify_internal(
 			}
  			break;
 
+		case SLAP_MOD_SOFTDEL:
+			Debug(LDAP_DEBUG_ARGS,
+				"bdb_modify_internal: softdel %s\n",
+				mod->sm_desc->ad_cname.bv_val, 0, 0);
+ 			/* Avoid problems in index_delete_mods()
+ 			 * We need to add index if necessary.
+ 			 */
+ 			mod->sm_op = LDAP_MOD_DELETE;
+
+			err = modify_delete_values( e, mod, get_permissiveModify(op),
+				text, textbuf, textlen );
+
+ 			mod->sm_op = SLAP_MOD_SOFTDEL;
+
+ 			if ( err == LDAP_TYPE_OR_VALUE_EXISTS ) {
+ 				err = LDAP_SUCCESS;
+ 			}
+
+			if( err != LDAP_SUCCESS ) {
+				Debug(LDAP_DEBUG_ARGS, "bdb_modify_internal: %d %s\n",
+					err, *text, 0);
+			}
+ 			break;
+
+		case SLAP_MOD_ADD_IF_NOT_PRESENT:
+			if ( attr_find( e->e_attrs, mod->sm_desc ) != NULL ) {
+				/* skip */
+				err = LDAP_SUCCESS;
+				break;
+			}
+
+			Debug(LDAP_DEBUG_ARGS,
+				"bdb_modify_internal: add_if_not_present %s\n",
+				mod->sm_desc->ad_cname.bv_val, 0, 0);
+ 			/* Avoid problems in index_add_mods()
+ 			 * We need to add index if necessary.
+ 			 */
+ 			mod->sm_op = LDAP_MOD_ADD;
+
+			err = modify_add_values( e, mod, get_permissiveModify(op),
+				text, textbuf, textlen );
+
+ 			mod->sm_op = SLAP_MOD_ADD_IF_NOT_PRESENT;
+
+			if( err != LDAP_SUCCESS ) {
+				Debug(LDAP_DEBUG_ARGS, "bdb_modify_internal: %d %s\n",
+					err, *text, 0);
+			}
+ 			break;
+
 		default:
 			Debug(LDAP_DEBUG_ANY, "bdb_modify_internal: invalid op %d\n",
 				mod->sm_op, 0, 0);
diff --git a/servers/slapd/back-ldif/ldif.c b/servers/slapd/back-ldif/ldif.c
index e4ff2fe8908c71257770c21360d6e81e6acf743b..5e7cdc9d3acb0a116802118edebe3e1058f1ebf5 100644
--- a/servers/slapd/back-ldif/ldif.c
+++ b/servers/slapd/back-ldif/ldif.c
@@ -1198,6 +1198,31 @@ apply_modify_to_entry(
 				rc = LDAP_SUCCESS;
 			}
 			break;
+
+		case SLAP_MOD_SOFTDEL:
+			mods->sm_op = LDAP_MOD_DELETE;
+			rc = modify_delete_values(entry, mods,
+				   get_permissiveModify(op),
+				   &rs->sr_text, textbuf,
+				   sizeof( textbuf ) );
+			mods->sm_op = SLAP_MOD_SOFTDEL;
+			if (rc == LDAP_NO_SUCH_ATTRIBUTE) {
+				rc = LDAP_SUCCESS;
+			}
+			break;
+
+		case SLAP_MOD_ADD_IF_NOT_PRESENT:
+			if ( attr_find( entry->e_attrs, mods->sm_desc ) ) {
+				rc = LDAP_SUCCESS;
+				break;
+			}
+			mods->sm_op = LDAP_MOD_ADD;
+			rc = modify_add_values(entry, mods,
+				   get_permissiveModify(op),
+				   &rs->sr_text, textbuf,
+				   sizeof( textbuf ) );
+			mods->sm_op = SLAP_MOD_ADD_IF_NOT_PRESENT;
+			break;
 		}
 		if(rc != LDAP_SUCCESS) break;
 	}
diff --git a/servers/slapd/back-ndb/modify.cpp b/servers/slapd/back-ndb/modify.cpp
index 188826140cc3ae399f7eddad6276778b8f4daa51..76b0ce71f668dc278dba5efa0b61a33979db77d4 100644
--- a/servers/slapd/back-ndb/modify.cpp
+++ b/servers/slapd/back-ndb/modify.cpp
@@ -300,6 +300,49 @@ int ndb_modify_internal(
 			}
  			break;
 
+		case SLAP_MOD_SOFTDEL:
+			Debug(LDAP_DEBUG_ARGS,
+				"ndb_modify_internal: softdel %s\n",
+				mod->sm_desc->ad_cname.bv_val, 0, 0);
+ 			mod->sm_op = LDAP_MOD_DELETE;
+
+			rc = modify_delete_values( NA->e, mod, get_permissiveModify(op),
+				text, textbuf, textlen );
+
+ 			mod->sm_op = SLAP_MOD_SOFTDEL;
+
+ 			if ( rc == LDAP_NO_SUCH_ATTRIBUTE) {
+ 				rc = LDAP_SUCCESS;
+ 			}
+
+			if( rc != LDAP_SUCCESS ) {
+				Debug(LDAP_DEBUG_ARGS, "ndb_modify_internal: %d %s\n",
+					rc, *text, 0);
+			}
+ 			break;
+
+		case SLAP_MOD_ADD_IF_NOT_PRESENT:
+			Debug(LDAP_DEBUG_ARGS,
+				"ndb_modify_internal: add_if_not_present %s\n",
+				mod->sm_desc->ad_cname.bv_val, 0, 0);
+			if ( attr_find( NA->e->e_attrs, mod->sm_desc ) ) {
+				rc = LDAP_SUCCESS;
+				break;
+			}
+
+ 			mod->sm_op = LDAP_MOD_ADD;
+
+			rc = modify_add_values( NA->e, mod, get_permissiveModify(op),
+				text, textbuf, textlen );
+
+ 			mod->sm_op = SLAP_MOD_ADD_IF_NOT_PRESENT;
+
+			if( rc != LDAP_SUCCESS ) {
+				Debug(LDAP_DEBUG_ARGS, "ndb_modify_internal: %d %s\n",
+					rc, *text, 0);
+			}
+ 			break;
+
 		default:
 			Debug(LDAP_DEBUG_ANY, "ndb_modify_internal: invalid op %d\n",
 				mod->sm_op, 0, 0);
diff --git a/servers/slapd/back-sql/add.c b/servers/slapd/back-sql/add.c
index 5e26cae2d4b0850a75e4d0eb0533e9b7c3d9ef31..af617000fb4c85743a7dbb9b53377ddd1f534dfe 100644
--- a/servers/slapd/back-sql/add.c
+++ b/servers/slapd/back-sql/add.c
@@ -389,6 +389,7 @@ del_all:
 		 */
 		case LDAP_MOD_ADD:
 		/* case SLAP_MOD_SOFTADD: */
+		/* case SLAP_MOD_ADD_IF_NOT_PRESENT: */
 add_only:;
 			if ( at->bam_add_proc == NULL ) {
 				Debug( LDAP_DEBUG_TRACE,
@@ -541,6 +542,7 @@ add_only:;
 			break;
 			
 	      	case LDAP_MOD_DELETE:
+		/* case SLAP_MOD_SOFTDEL: */
 			if ( at->bam_delete_proc == NULL ) {
 				Debug( LDAP_DEBUG_TRACE,
 					"   backsql_modify_internal(): "
diff --git a/servers/slapd/bconfig.c b/servers/slapd/bconfig.c
index 006647bf23f0ea76d5db4c4bc64b9098a9ee64c7..3a30d1471d2c9e7eb77072473dbeb67df0dc8bc5 100644
--- a/servers/slapd/bconfig.c
+++ b/servers/slapd/bconfig.c
@@ -5468,7 +5468,9 @@ config_modify_internal( CfEntryInfo *ce, Operation *op, SlapReply *rs,
 		ct = config_find_table( colst, nocs, ml->sml_desc, ca );
 		switch (ml->sml_op) {
 		case LDAP_MOD_DELETE:
-		case LDAP_MOD_REPLACE: {
+		case LDAP_MOD_REPLACE:
+		case SLAP_MOD_SOFTDEL:
+		{
 			BerVarray vals = NULL, nvals = NULL;
 			int *idx = NULL;
 			if ( ct && ( ct->arg_type & ARG_NO_DELETE )) {
@@ -5507,11 +5509,24 @@ config_modify_internal( CfEntryInfo *ce, Operation *op, SlapReply *rs,
 				ml->sml_values = vals;
 				ml->sml_nvalues = nvals;
 			}
+			if ( rc == LDAP_NO_SUCH_ATTRIBUTE && ml->sml_op == SLAP_MOD_SOFTDEL )
+			{
+				rc = LDAP_SUCCESS;
+			}
+			/* FIXME: check rc before fallthru? */
 			if ( !vals )
 				break;
-			}
+		}
 			/* FALLTHRU: LDAP_MOD_REPLACE && vals */
 
+		case SLAP_MOD_ADD_IF_NOT_PRESENT:
+			if ( ml->sml_op == SLAP_MOD_ADD_IF_NOT_PRESENT
+				&& attr_find( e->e_attrs, ml->sml_desc ) )
+			{
+				rc = LDAP_SUCCESS;
+				break;
+			}
+
 		case LDAP_MOD_ADD:
 		case SLAP_MOD_SOFTADD: {
 			int mop = ml->sml_op;
diff --git a/servers/slapd/modify.c b/servers/slapd/modify.c
index 32a39d97fb9be2b565c1e88bc8f2720881043374..14b6b37305ac2f5488f52ea352eeb33b4d50d678 100644
--- a/servers/slapd/modify.c
+++ b/servers/slapd/modify.c
@@ -879,6 +879,7 @@ void slap_mods_opattrs(
 		for ( modtail = modsp; *modtail; modtail = &(*modtail)->sml_next ) {
 			if ( (*modtail)->sml_op != LDAP_MOD_ADD &&
 				(*modtail)->sml_op != SLAP_MOD_SOFTADD &&
+				(*modtail)->sml_op != SLAP_MOD_ADD_IF_NOT_PRESENT &&
 				(*modtail)->sml_op != LDAP_MOD_REPLACE )
 			{
 				continue;
diff --git a/servers/slapd/overlays/constraint.c b/servers/slapd/overlays/constraint.c
index b3dbbae5c4d64b331c0f7ad3348a7647700fc403..fcb2830ee88cbf2e45d344ba3537fdfac10ee36e 100644
--- a/servers/slapd/overlays/constraint.c
+++ b/servers/slapd/overlays/constraint.c
@@ -1022,6 +1022,29 @@ constraint_update( Operation *op, SlapReply *rs )
  							}
 							break;
 
+						case SLAP_MOD_SOFTDEL:
+ 							mod->sm_op = LDAP_MOD_ADD;
+							err = modify_delete_values( target_entry_copy,
+								mod, get_permissiveModify(op),
+								&text, textbuf, textlen );
+ 							mod->sm_op = SLAP_MOD_SOFTDEL;
+ 							if ( err == LDAP_NO_SUCH_ATTRIBUTE ) {
+ 								err = LDAP_SUCCESS;
+ 							}
+							break;
+
+						case SLAP_MOD_ADD_IF_NOT_PRESENT:
+							if ( attr_find( target_entry_copy->e_attrs, mod->sm_desc ) ) {
+								err = LDAP_SUCCESS;
+								break;
+							}
+ 							mod->sm_op = LDAP_MOD_ADD;
+							err = modify_add_values( target_entry_copy,
+								mod, get_permissiveModify(op),
+								&text, textbuf, textlen );
+ 							mod->sm_op = SLAP_MOD_ADD_IF_NOT_PRESENT;
+							break;
+
 						default:
 							err = LDAP_OTHER;
 							break;
diff --git a/servers/slapd/overlays/dds.c b/servers/slapd/overlays/dds.c
index 6185ad8d52bd226e81642f634048ae565a5efa14..56d299fdb0142148d5c7df7895458bab568b3100 100644
--- a/servers/slapd/overlays/dds.c
+++ b/servers/slapd/overlays/dds.c
@@ -587,6 +587,7 @@ dds_op_modify( Operation *op, SlapReply *rs )
 
 			switch ( mod->sml_op ) {
 			case LDAP_MOD_DELETE:
+			case SLAP_MOD_SOFTDEL: /* FIXME? */
 				if ( mod->sml_values != NULL ) {
 					if ( BER_BVISEMPTY( &bv_entryTtl ) 
 						|| !bvmatch( &bv_entryTtl, &mod->sml_values[ 0 ] ) )
@@ -611,8 +612,9 @@ dds_op_modify( Operation *op, SlapReply *rs )
 				entryTtl = -1;
 				/* fallthru */
 
-			case SLAP_MOD_SOFTADD: /* FIXME? */
 			case LDAP_MOD_ADD:
+			case SLAP_MOD_SOFTADD: /* FIXME? */
+			case SLAP_MOD_ADD_IF_NOT_PRESENT: /* FIXME? */
 				assert( mod->sml_values != NULL );
 				assert( BER_BVISNULL( &mod->sml_values[ 1 ] ) );
 
diff --git a/servers/slapd/slap.h b/servers/slapd/slap.h
index 80509e11dfffa4b68d7fde969088ae71c094c2e1..745402aa1ccf544bf7dd8cfcb8e3272f359e7887 100644
--- a/servers/slapd/slap.h
+++ b/servers/slapd/slap.h
@@ -110,12 +110,25 @@ LDAP_BEGIN_DECL
 # define SLAP_STRING_UNKNOWN	"unknown"
 #endif /* ! TCP Wrappers */
 
-/* LDAPMod.mod_op value ===> Must be kept in sync with ldap.h!
- * This is a value used internally by the backends. It is needed to allow
- * adding values that already exist without getting an error as required by
- * modrdn when the new rdn was already an attribute value itself.
+/* LDAPMod.mod_op value ===> Must be kept in sync with ldap.h! */
+/* These values are used internally by the backends. */
+/* SLAP_MOD_SOFTADD allows adding values that already exist without getting
+ * an error as required by modrdn when the new rdn was already an attribute
+ * value itself.
+ */
+#define SLAP_MOD_SOFTADD		0x1000
+/* SLAP_MOD_SOFTDEL allows deleting values if they exist without getting
+ * an error otherwise.
+ */
+#define SLAP_MOD_SOFTDEL		0x1001
+/* SLAP_MOD_ADD_IF_NOT_PRESENT allows adding values unless the attribute
+ * is already present without getting an error.
+ */
+#define SLAP_MOD_ADD_IF_NOT_PRESENT	0x1002
+/* SLAP_MOD_DEL_IF_PRESENT allows deleting values if the attribute
+ * is present, without getting an error otherwise.
+ * The semantics can be obtained using SLAP_MOD_SOFTDEL with NULL values.
  */
-#define SLAP_MOD_SOFTADD	0x1000
 
 #define MAXREMATCHES (100)