Commit 3f0f9950 authored by Ondřej Kuzník's avatar Ondřej Kuzník
Browse files

ITS#8882 Add slapo-eds to contrib

parent f5f5231d
Pipeline #4309 passed with stage
in 44 minutes and 58 seconds
# test suite
clients
servers
# $OpenLDAP$
# This work is part of OpenLDAP Software <http://www.openldap.org/>.
#
# Copyright 1998-2022 The OpenLDAP Foundation.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted only as authorized by the OpenLDAP
# Public License.
#
# A copy of this license is available in the file LICENSE in the
# top-level directory of the distribution or, alternatively, at
# <http://www.OpenLDAP.org/license.html>.
LDAP_SRC = ../../..
LDAP_BUILD = $(LDAP_SRC)
SRCDIR = ./
LDAP_INC = -I$(LDAP_BUILD)/include -I$(LDAP_SRC)/include -I$(LDAP_SRC)/servers/slapd
LDAP_LIB = $(LDAP_BUILD)/libraries/libldap/libldap.la \
$(LDAP_BUILD)/libraries/liblber/liblber.la
LIBTOOL = $(LDAP_BUILD)/libtool
INSTALL = /usr/bin/install
CC = gcc
OPT = -g -O2
DEFS = -DSLAPD_OVER_EDS=SLAPD_MOD_DYNAMIC
INCS = $(LDAP_INC)
LIBS = $(LDAP_LIB)
PROGRAMS = eds.la
MANPAGES = slapo-eds.5
CLEAN = *.o *.lo *.la .libs
LTVER = 0:0:0
prefix=/usr/local
exec_prefix=$(prefix)
ldap_subdir=/openldap
libdir=$(exec_prefix)/lib
libexecdir=$(exec_prefix)/libexec
moduledir = $(libexecdir)$(ldap_subdir)
mandir = $(exec_prefix)/share/man
man5dir = $(mandir)/man5
all: $(PROGRAMS)
d :=
sp :=
dir := tests
include $(dir)/Rules.mk
.SUFFIXES: .c .o .lo
.c.lo:
$(LIBTOOL) --mode=compile $(CC) $(CFLAGS) $(OPT) $(CPPFLAGS) $(DEFS) $(INCS) -c $<
all: $(PROGRAMS)
eds.la: eds.lo
$(LIBTOOL) --mode=link $(CC) $(LDFLAGS) -version-info $(LTVER) \
-rpath $(moduledir) -module -o $@ $? $(LIBS)
clean:
rm -rf $(CLEAN)
install: install-lib install-man FORCE
install-lib: $(PROGRAMS)
mkdir -p $(DESTDIR)$(moduledir)
for p in $(PROGRAMS) ; do \
$(LIBTOOL) --mode=install cp $$p $(DESTDIR)$(moduledir) ; \
done
install-man: $(MANPAGES)
mkdir -p $(DESTDIR)$(man5dir)
$(INSTALL) -m 644 $(MANPAGES) $(DESTDIR)$(man5dir)
FORCE:
eds Overlay README
DESCRIPTION
This package contains an OpenLDAP overlay called "eds" (empty
directory string) that eliminates empty values of type directory string
(OID 1.3.6.1.4.1.1466.115.121.1.15) from the list of the values in the
following manner:
- add: All empty attribute values will be removed before the add request
is executed
- mod-replace: A replace with empty values will be modified to a replace
without values. As result the attribute will be deleted
- mod-add: All empty attribute values will be removed before the mod-add
request is executed
- mod-delete: All empty attribute values will be removed before the
mod-delete request is executed
At module load time the eds overlay manipulates the syntax checking
so that it intercepts the syntax check and allows empty values for
attributes of type directory string only. Non-empty values continue to
go through the normal check routines. It is therefore very important to
configure the overlays in a way that ensures that the eds overlay gets
the control over the operation before any other overlay. Otherwise it
could come to the situation with empty attribute values in the data base.
David Hawes' addparial overlay has been used as starting point for this
overlay.
BUILDING
A Makefile is included, please set your LDAP_SRC directory properly.
INSTALLATION
After compiling the eds overlay, add the following to your
slapd.conf:
### slapd.conf
...
moduleload eds.la
...
overlay eds
...
# before database directive...
# this overlay must be the last overlay in the config file to ensure that
# requests are modified before other overlays get them.
...
### end slapd.conf
CAVEATS
- In order to ensure that eds does what it needs to do, it must be
the last overlay configured so it will run before the other overlays.
---
Copyright 2014-2022 The OpenLDAP Foundation.
Portions Copyright (C) DAASI International GmbH, Tamim Ziai.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted only as authorized by the OpenLDAP
Public License.
A copy of this license is available in file LICENSE in the
top-level directory of the distribution or, alternatively, at
http://www.OpenLDAP.org/license.html.
/* eds.c */
/* $OpenLDAP$ */
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
*
* Copyright 2014-2022 The OpenLDAP Foundation.
* Portions Copyright (C) 2014 DAASI International GmbH, Tamim Ziai.
* Portions Copyright (C) 2022 Ondřej Kuzník, Symas Corporation.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* http://www.OpenLDAP.org/license.html.
*/
/* ACKNOLEDGEDMENTS:
* This work was initially developed by Tamim Ziai of DAASI International GmbH
* for inclusion in OpenLDAP Software.
*/
/* slapo-eds
*
* This is an OpenLDAP overlay that accepts empty strings as attribute values
* without syntax violation but never actually stores them. This allows
* applications that used to work with LDAP implementations allowing empty
* strings (such as Novel eDirectory) to continue to work with OpenLDAP without
* any modifications. Add and modify change types will be proceeded as follows,
* other operations will be forwarded without modifications:
*
* changeType: add changeType: add
* sn: <empty> --> sn: blah
* sn: blah
*
* changeType: modify changeType: modify
* add: sn --> add: sn
* sn: <empty> sn: blah
* sn: blah
*
* changeType: modify changeType: modify
* delete: sn --> delete: sn
* sn: <empty> sn: blah
* sn: blah
*
* changeType: modify changeType: modify
* replace: sn --> replace: sn
* sn: <empty>
*
*/
#include "portable.h"
#include "slap.h"
static slap_overinst eds;
static const char ds_oid[] = "1.3.6.1.4.1.1466.115.121.1.15";
static slap_syntax_validate_func *ssyn_validate_original = NULL;
static slap_syntax_transform_func *ssyn_pretty_original = NULL;
static int eds_instances = 0;
static unsigned int
remove_empty_values( Modification *m, Attribute *a )
{
BerVarray vals = m ? m->sm_values : a->a_vals,
nvals = m ? m->sm_nvalues : a->a_nvals;
unsigned int i, j, numvals = m ? m->sm_numvals : a->a_numvals;
for ( i = 0; i < numvals && !BER_BVISEMPTY( &vals[i] ); i++ )
/* Find first empty */;
if ( i == numvals ) return i;
/*
* We have an empty value at index i, move all of them to the end of the
* list, preserving the order of non-empty values.
*/
j = i + 1;
for ( j = i + 1; j < numvals; j++ ) {
struct berval tmp;
if ( BER_BVISEMPTY( &vals[j] ) ) continue;
tmp = vals[i];
vals[i] = vals[j];
vals[j] = tmp;
if ( nvals && vals != nvals ) {
tmp = nvals[i];
nvals[i] = nvals[j];
nvals[j] = tmp;
}
if ( m && a && m->sm_values != a->a_vals ) {
tmp = a->a_vals[i];
a->a_vals[i] = a->a_vals[j];
a->a_vals[j] = tmp;
if ( a->a_nvals && a->a_vals != a->a_nvals ) {
tmp = a->a_nvals[i];
a->a_nvals[i] = a->a_nvals[j];
a->a_nvals[j] = tmp;
}
}
i++;
}
/* Free empty vals */
for ( ; j && i < j--; ) {
ber_memfree( vals[j].bv_val );
if ( nvals && vals != nvals ) {
ber_memfree( nvals[j].bv_val );
BER_BVZERO( &nvals[j] );
}
if ( m && a && m->sm_values != a->a_vals ) {
if ( m->sm_values[j].bv_val != a->a_vals[j].bv_val ) {
ber_memfree( a->a_vals[j].bv_val );
BER_BVZERO( &a->a_vals[j] );
if ( a->a_nvals && a->a_vals != a->a_nvals ) {
ber_memfree( a->a_nvals[j].bv_val );
BER_BVZERO( &a->a_nvals[j] );
}
}
}
BER_BVZERO( &vals[j] );
}
return i;
}
/**
* Remove all operations with empty strings.
*/
static int
eds_op_add( Operation *op, SlapReply *rs )
{
Attribute **ap, **nexta, *a;
Modifications **mlp, **nextp = NULL, *ml;
Entry *e = op->oq_add.rs_e;
/*
* op->ora_modlist can be NULL, at least accesslog doesn't always populate
* it on an add.
*/
for ( ap = &e->e_attrs, a = e->e_attrs, mlp = &op->ora_modlist,
ml = op->ora_modlist;
a != NULL;
ap = nexta, a = *ap, mlp = nextp, ml = ml ? *mlp : NULL ) {
AttributeType *at = a->a_desc->ad_type;
unsigned int remaining;
nexta = &a->a_next;
if ( ml ) {
nextp = &ml->sml_next;
}
if ( at->sat_syntax != slap_schema.si_syn_directoryString ||
at->sat_atype.at_usage != LDAP_SCHEMA_USER_APPLICATIONS )
continue;
remaining = remove_empty_values( &ml->sml_mod, a );
if ( remaining == a->a_numvals ) continue;
/* Empty values found */
if ( !remaining ) {
/* All values are empty */
*ap = a->a_next;
a->a_next = NULL;
nexta = ap;
if ( ml ) {
*mlp = ml->sml_next;
ml->sml_next = NULL;
nextp = mlp;
/* Values are generally shared with attribute */
slap_mods_free( ml, ml->sml_values != a->a_vals );
}
attr_free( a );
} else {
a->a_numvals = remaining;
if ( ml ) {
ml->sml_mod.sm_numvals = remaining;
}
}
}
return SLAP_CB_CONTINUE;
}
static int
eds_op_modify( Operation *op, SlapReply *rs )
{
Modifications **mlp, **nextp, *ml;
for ( mlp = &op->orm_modlist, ml = op->orm_modlist; ml != NULL;
mlp = nextp, ml = *mlp ) {
AttributeType *at = ml->sml_desc->ad_type;
unsigned int remaining;
nextp = &ml->sml_next;
if ( at->sat_syntax != slap_schema.si_syn_directoryString ||
at->sat_atype.at_usage != LDAP_SCHEMA_USER_APPLICATIONS )
continue;
remaining = remove_empty_values( &ml->sml_mod, NULL );
if ( remaining == ml->sml_numvals ) continue;
if ( !remaining ) {
/* All values are empty */
if ( ml->sml_op == LDAP_MOD_REPLACE ) {
/* Replace is kept */
if ( ml->sml_nvalues && ml->sml_nvalues != ml->sml_values ) {
ber_bvarray_free( ml->sml_nvalues );
}
if ( ml->sml_values ) {
ber_bvarray_free( ml->sml_values );
}
ml->sml_numvals = 0;
ml->sml_values = NULL;
ml->sml_nvalues = NULL;
} else {
/* Remove modification */
*mlp = ml->sml_next;
ml->sml_next = NULL;
nextp = mlp;
slap_mods_free( ml, 1 );
}
} else {
ml->sml_numvals = remaining;
}
}
return SLAP_CB_CONTINUE;
}
static int
eds_ssyn_validate( Syntax *syntax, struct berval *in )
{
if ( BER_BVISEMPTY( in ) && syntax == slap_schema.si_syn_directoryString ) {
return LDAP_SUCCESS;
}
return ssyn_validate_original( syntax, in );
}
static int
eds_ssyn_pretty( Syntax *syntax,
struct berval *in,
struct berval *out,
void *memctx )
{
if ( BER_BVISEMPTY( in ) && syntax == slap_schema.si_syn_directoryString ) {
return LDAP_SUCCESS;
}
return ssyn_pretty_original( syntax, in, out, memctx );
}
static int
eds_db_init( BackendDB *be, ConfigReply *cr )
{
Syntax *syntax = syn_find( ds_oid );
if ( syntax == NULL ) {
Debug( LDAP_DEBUG_TRACE, "eds_db_init: "
"Syntax %s not found\n",
ds_oid );
} else {
Debug( LDAP_DEBUG_TRACE, "eds_db_init: "
"Found syntax: %s\n",
syntax->ssyn_bvoid.bv_val );
if ( ssyn_validate_original == NULL && syntax->ssyn_validate != NULL ) {
ssyn_validate_original = syntax->ssyn_validate;
syntax->ssyn_validate = eds_ssyn_validate;
}
if ( ssyn_pretty_original == NULL && syntax->ssyn_pretty != NULL ) {
ssyn_pretty_original = syntax->ssyn_pretty;
syntax->ssyn_pretty = &eds_ssyn_pretty;
}
}
eds_instances++;
return LDAP_SUCCESS;
}
static int
eds_db_destroy( BackendDB *be, ConfigReply *cr )
{
Syntax *syntax = syn_find( ds_oid );
if ( --eds_instances == 0 && syntax != NULL ) {
if ( syntax->ssyn_validate == eds_ssyn_validate ) {
syntax->ssyn_validate = ssyn_validate_original;
}
ssyn_validate_original = NULL;
if ( syntax->ssyn_pretty == eds_ssyn_pretty ) {
syntax->ssyn_pretty = ssyn_pretty_original;
}
ssyn_pretty_original = NULL;
}
assert( eds_instances >= 0 );
return LDAP_SUCCESS;
}
int
eds_init()
{
eds.on_bi.bi_type = "eds";
eds.on_bi.bi_op_add = eds_op_add;
eds.on_bi.bi_op_modify = eds_op_modify;
eds.on_bi.bi_db_init = eds_db_init;
eds.on_bi.bi_db_destroy = eds_db_destroy;
return overlay_register( &eds );
}
int
init_module( int argc, char *argv[] )
{
return eds_init();
}
.TH SLAPO-EDS 5 "RELEASEDATE" "OpenLDAP LDVERSION"
.\" Copyright 2022 The OpenLDAP Foundation, All Rights Reserved.
.\" Copyright 2018 Tamim Ziai
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
.\" $OpenLDAP$
.SH NAME
slapo-eds \- Remove Empty values from Directory String attributes
Overlay to slapd
.SH SYNOPSIS
ETCDIR/slapd.conf
.SH DESCRIPTION
Some broken client will provide empty values for Directory String attributes
with certain operations. This overlay makes empty values acceptable for the
Directory String syntax and will adjust all operations to make sure these
values are never actually be stored in the database.
.LP
.nf
.ft tt
dn: cn=alex,cn=people,dc=example,dc=org
changeType: add changeType: add
sn: <empty> --> sn: blah
sn: blah
dn: cn=alex,cn=people,dc=example,dc=org
changeType: modify changeType: modify
add: sn --> add: sn
sn: <empty> sn: blah
sn: blah
dn: cn=alex,cn=people,dc=example,dc=org
changeType: modify changeType: modify
delete: sn --> delete: sn
sn: <empty> sn: blah
sn: blah
dn: cn=alex,cn=people,dc=example,dc=org
changeType: modify changeType: modify
replace: sn --> replace: sn
sn: <empty>
dn: cn=alex,cn=people,dc=example,dc=org
changeType: modify changeType: modify
replace: sn --> replace: sn
sn: <empty> sn: blah
sn: blah
.ft
.fi
.LP
.SH CONFIGURATION
This overlay has no specific configuration, however In order to ensure that it
does what it needs to do, it should be the last overlay configured so it will
run before the other overlays.
.SH EXAMPLES
.LP
.RS
.nf
overlay eds
.RE
.SH FILES
.TP
ETCDIR/slapd.conf
default slapd configuration file
.SH SEE ALSO
.BR slapd.conf (5).
.SH ACKNOWLEDGEMENTS
This module was written in 2014 by Tamim Ziai for DAASI International and
updated in 2022 by Ondřej Kuzník for inclusion in the OpenLDAP project.
.so ../Project
sp := $(sp).x
dirstack_$(sp) := $(d)
d := $(dir)
.PHONY: test
CLEAN += clients servers tests/progs tests/schema tests/testdata tests/testrun
test: all clients servers tests/progs
test:
cd tests; \
SRCDIR=$(abspath $(LDAP_SRC)) \
LDAP_BUILD=$(abspath $(LDAP_BUILD)) \
TOPDIR=$(abspath $(SRCDIR)) \
LIBTOOL=$(abspath $(LIBTOOL)) \
$(abspath $(SRCDIR))/tests/run all
servers clients tests/progs:
ln -s $(abspath $(LDAP_BUILD))/$@ $@
d := $(dirstack_$(sp))
sp := $(basename $(sp))
# basic slapd config -- for testing of slapo-eds
# $OpenLDAP$
## This work is part of OpenLDAP Software <http://www.openldap.org/>.
##
## Copyright 1998-2022 The OpenLDAP Foundation.
## All rights reserved.
##
## Redistribution and use in source and binary forms, with or without
## modification, are permitted only as authorized by the OpenLDAP
## Public License.
##
## A copy of this license is available in the file LICENSE in the
## top-level directory of the distribution or, alternatively, at
## <http://www.OpenLDAP.org/license.html>.
include @SCHEMADIR@/core.schema
include @SCHEMADIR@/cosine.schema
include @SCHEMADIR@/inetorgperson.schema
include @SCHEMADIR@/openldap.schema
include @SCHEMADIR@/nis.schema
include @DATADIR@/test.schema
#
pidfile @TESTDIR@/slapd.1.pid
argsfile @TESTDIR@/slapd.1.args
#mod#modulepath ../servers/slapd/back-@BACKEND@/
#mod#moduleload back_@BACKEND@.la
#accesslogmod#modulepath ../servers/slapd/overlays/
#accesslogmod#moduleload accesslog.la
moduleload ../eds.la
database @BACKEND@
suffix "dc=example,dc=com"
rootdn "cn=Manager,dc=example,dc=com"
rootpw secret
#~null~#directory @TESTDIR@/db.1.a
overlay accesslog
logdb cn=log
logops writes
logsuccess true
overlay eds
database @BACKEND@