mirror of
https://git.openldap.org/openldap/openldap.git
synced 2025-02-23 14:09:39 +08:00
ITS#9817 Introduce DN and filter manipulation tools
This commit is contained in:
parent
598929a507
commit
56877e333b
@ -243,7 +243,7 @@ code set to
|
||||
.BR n ;
|
||||
or, in other words, `@' is equivalent to `U{0}'.
|
||||
Positive errors are allowed, indicating the related LDAP error codes
|
||||
as specified in \fIdraft-ietf-ldapbis-protocol\fP.
|
||||
as specified in \fIRFC4511\fP.
|
||||
.LP
|
||||
The ordering of the flags can be significant.
|
||||
For instance: `IG{2}' means ignore errors and jump two lines ahead
|
||||
@ -492,6 +492,37 @@ LDAP map, the
|
||||
portion must contain exactly one attribute, and if
|
||||
a multi-valued attribute is used, only the first value is considered.
|
||||
|
||||
.TP
|
||||
.B escape [escape2dn|escape2filter|unescapedn|unescapefilter]...
|
||||
The
|
||||
.B escape
|
||||
map makes it possible use DNs or their parts in filter strings and vice versa.
|
||||
It processes a value according to the operations listed in order. Supported
|
||||
operations include:
|
||||
|
||||
.RS
|
||||
.TP
|
||||
.B escape2dn
|
||||
takes a string and escapes it so it can safely be pasted in a DN
|
||||
.TP
|
||||
.B escape2filter
|
||||
takes a string and escapes it so it can safely be pasted in a filter
|
||||
.TP
|
||||
.B unescapedn
|
||||
takes a string and undoes DN escaping
|
||||
.TP
|
||||
.B unescapefilter
|
||||
takes a string and undoes filter escaping
|
||||
.RE
|
||||
|
||||
.RS
|
||||
It is advised that each
|
||||
.B escape
|
||||
map ends with an
|
||||
.B escape
|
||||
operation as that is the only safe way to handle arbitrary strings.
|
||||
.RE
|
||||
|
||||
.SH "REWRITE CONFIGURATION EXAMPLES"
|
||||
.nf
|
||||
# set to `off' to disable rewriting
|
||||
@ -566,6 +597,9 @@ rwm\-rewriteContext searchEntryDN
|
||||
rwm\-rewriteRule "(.*[^ ],)?[ ]?dc=OpenLDAP,[ ]?dc=org$"
|
||||
"${>eatBlanks($1)}dc=home,dc=net" ":"
|
||||
|
||||
# Transform a DN value such that it can be used in a filter
|
||||
rwm\-rewriteMap escape dn2filter unescapedn escape2filter
|
||||
|
||||
# Bind with email instead of full DN: we first need
|
||||
# an ldap map that turns attributes into a DN (the
|
||||
# argument used when invoking the map is appended to
|
||||
@ -579,8 +613,13 @@ rwm\-rewriteMap ldap attr2dn "ldap://host/dc=my,dc=org?dn?sub"
|
||||
# to real naming contexts, we also need to rewrite
|
||||
# regular DNs, because the definition of a bindDN
|
||||
# rewrite context overrides the default definition.
|
||||
#
|
||||
# While actual email addresses tend not to contain filter
|
||||
# special characters, the provided Bind DN has no such
|
||||
# restrictions.
|
||||
rwm\-rewriteContext bindDN
|
||||
rwm\-rewriteRule "^mail=[^,]+@[^,]+$" "${attr2dn($0)}" ":@I"
|
||||
rwm\-rewriteRule "^(mail=)([^,]+@[^,]+)$"
|
||||
"${attr2dn($1${dn2filter($2)})}" ":@I"
|
||||
|
||||
# This is a rather sophisticated example. It massages a
|
||||
# search filter in case who performs the search has
|
||||
|
@ -17,11 +17,11 @@
|
||||
##
|
||||
|
||||
SRCS = config.c context.c info.c ldapmap.c map.c params.c rule.c \
|
||||
session.c subst.c var.c xmap.c \
|
||||
session.c subst.c var.c xmap.c escapemap.c \
|
||||
parse.c rewrite.c
|
||||
XSRCS = version.c
|
||||
OBJS = config.o context.o info.o ldapmap.o map.o params.o rule.o \
|
||||
session.o subst.o var.o xmap.o
|
||||
session.o subst.o var.o xmap.o escapemap.o
|
||||
|
||||
LDAP_INCDIR= ../../include
|
||||
LDAP_LIBDIR= ../../libraries
|
||||
|
221
libraries/librewrite/escapemap.c
Normal file
221
libraries/librewrite/escapemap.c
Normal file
@ -0,0 +1,221 @@
|
||||
/* $OpenLDAP$ */
|
||||
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
|
||||
*
|
||||
* Copyright 2000-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>.
|
||||
*/
|
||||
/* ACKNOWLEDGEMENT:
|
||||
* This work was initially developed by Ondřej Kuzník for inclusion in OpenLDAP
|
||||
* Software.
|
||||
*/
|
||||
|
||||
#include <portable.h>
|
||||
|
||||
#define LDAP_DEPRECATED 1
|
||||
#include "rewrite-int.h"
|
||||
#include "rewrite-map.h"
|
||||
|
||||
#include <ldap_pvt.h>
|
||||
|
||||
typedef int (escape_fn)( struct berval *input, struct berval *output );
|
||||
|
||||
/*
|
||||
* Map configuration, a NULL-terminated list of escape_fn pointers
|
||||
*/
|
||||
struct escape_map_data {
|
||||
escape_fn **fn;
|
||||
};
|
||||
|
||||
/*
|
||||
* (un)escape functions
|
||||
*/
|
||||
|
||||
static int
|
||||
map_escape_to_filter( struct berval *input, struct berval *output )
|
||||
{
|
||||
return ldap_bv2escaped_filter_value( input, output );
|
||||
}
|
||||
|
||||
static int
|
||||
map_unescape_filter( struct berval *input, struct berval *output )
|
||||
{
|
||||
ber_slen_t len;
|
||||
|
||||
if ( ber_dupbv( output, input ) == NULL ) {
|
||||
return REWRITE_ERR;
|
||||
}
|
||||
|
||||
len = ldap_pvt_filter_value_unescape( output->bv_val );
|
||||
if ( len < 0 ) return REWRITE_ERR;
|
||||
output->bv_len = len;
|
||||
|
||||
return LDAP_SUCCESS;
|
||||
}
|
||||
|
||||
static int
|
||||
map_escape_to_dn( struct berval *input, struct berval *output )
|
||||
{
|
||||
LDAPAVA ava = { .la_attr = BER_BVC("uid"),
|
||||
.la_value = *input,
|
||||
.la_flags = LDAP_AVA_STRING },
|
||||
*ava_[] = { &ava, NULL };
|
||||
LDAPRDN rdn[] = { ava_, NULL };
|
||||
LDAPDN dn = rdn;
|
||||
struct berval dnstr;
|
||||
char *p;
|
||||
int rc;
|
||||
|
||||
rc = ldap_dn2bv( dn, &dnstr, LDAP_DN_FORMAT_LDAPV3 );
|
||||
if ( rc != LDAP_SUCCESS ) {
|
||||
return REWRITE_ERR;
|
||||
}
|
||||
|
||||
p = strchr( dnstr.bv_val, '=' );
|
||||
p++;
|
||||
|
||||
output->bv_len = dnstr.bv_len - ( p - dnstr.bv_val );
|
||||
output->bv_val = malloc( output->bv_len + 1 );
|
||||
if ( output->bv_val == NULL ) {
|
||||
free( dnstr.bv_val );
|
||||
return REWRITE_ERR;
|
||||
}
|
||||
memcpy( output->bv_val, p, output->bv_len );
|
||||
output->bv_val[output->bv_len] = '\0';
|
||||
|
||||
free( dnstr.bv_val );
|
||||
return REWRITE_SUCCESS;
|
||||
}
|
||||
|
||||
static int
|
||||
map_unescape_dn( struct berval *input, struct berval *output )
|
||||
{
|
||||
LDAPDN dn;
|
||||
struct berval fake_dn;
|
||||
char *p;
|
||||
int rc = REWRITE_SUCCESS;
|
||||
|
||||
fake_dn.bv_len = STRLENOF("uid=") + input->bv_len;
|
||||
fake_dn.bv_val = p = malloc( fake_dn.bv_len );
|
||||
if ( p == NULL ) {
|
||||
return REWRITE_ERR;
|
||||
}
|
||||
|
||||
memcpy( p, "uid=", STRLENOF("uid=") );
|
||||
p += STRLENOF("uid=");
|
||||
memcpy( p, input->bv_val, input->bv_len );
|
||||
|
||||
if ( ldap_bv2dn( &fake_dn, &dn, LDAP_DN_FORMAT_LDAPV3 ) != LDAP_SUCCESS ) {
|
||||
return REWRITE_ERR;
|
||||
}
|
||||
if ( ber_dupbv( output, &dn[0][0]->la_value ) == NULL ) {
|
||||
rc = REWRITE_ERR;
|
||||
}
|
||||
ldap_dnfree( dn );
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Registered callbacks */
|
||||
|
||||
static void *
|
||||
map_escape_parse(
|
||||
const char *fname,
|
||||
int lineno,
|
||||
int argc,
|
||||
char **argv
|
||||
)
|
||||
{
|
||||
escape_fn **fns;
|
||||
int i;
|
||||
|
||||
assert( fname != NULL );
|
||||
assert( argv != NULL );
|
||||
|
||||
if ( argc < 1 ) {
|
||||
Debug( LDAP_DEBUG_ANY,
|
||||
"[%s:%d] escape map needs at least one operation\n",
|
||||
fname, lineno );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fns = calloc( sizeof(escape_fn *), argc + 1 );
|
||||
if ( fns == NULL ) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for ( i = 0; i < argc; i++ ) {
|
||||
if ( strcasecmp( argv[i], "escape2dn" ) == 0 ) {
|
||||
fns[i] = map_escape_to_dn;
|
||||
} else if ( strcasecmp( argv[i], "escape2filter" ) == 0 ) {
|
||||
fns[i] = map_escape_to_filter;
|
||||
} else if ( strcasecmp( argv[i], "unescapedn" ) == 0 ) {
|
||||
fns[i] = map_unescape_dn;
|
||||
} else if ( strcasecmp( argv[i], "unescapefilter" ) == 0 ) {
|
||||
fns[i] = map_unescape_filter;
|
||||
} else {
|
||||
Debug( LDAP_DEBUG_ANY,
|
||||
"[%s:%d] unknown option %s (ignored)\n",
|
||||
fname, lineno, argv[i] );
|
||||
free( fns );
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return (void *)fns;
|
||||
}
|
||||
|
||||
static int
|
||||
map_escape_apply(
|
||||
void *private,
|
||||
const char *input,
|
||||
struct berval *output )
|
||||
{
|
||||
escape_fn **fns = private;
|
||||
struct berval tmpin, tmpout;
|
||||
int i;
|
||||
|
||||
assert( private != NULL );
|
||||
assert( input != NULL );
|
||||
assert( output != NULL );
|
||||
|
||||
ber_str2bv( input, 0, 1, &tmpin );
|
||||
|
||||
for ( i=0; fns[i]; i++ ) {
|
||||
int rc = fns[i]( &tmpin, &tmpout );
|
||||
free( tmpin.bv_val );
|
||||
if ( rc != REWRITE_SUCCESS ) {
|
||||
return rc;
|
||||
}
|
||||
tmpin = tmpout;
|
||||
}
|
||||
*output = tmpin;
|
||||
|
||||
return REWRITE_SUCCESS;
|
||||
}
|
||||
|
||||
static int
|
||||
map_escape_destroy(
|
||||
void *private
|
||||
)
|
||||
{
|
||||
struct ldap_map_data *data = private;
|
||||
|
||||
assert( private != NULL );
|
||||
free( data );
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const rewrite_mapper rewrite_escape_mapper = {
|
||||
"escape",
|
||||
map_escape_parse,
|
||||
map_escape_apply,
|
||||
map_escape_destroy
|
||||
};
|
@ -528,6 +528,9 @@ rewrite_map_destroy(
|
||||
/* ldapmap.c */
|
||||
extern const rewrite_mapper rewrite_ldap_mapper;
|
||||
|
||||
/* escapemap.c */
|
||||
extern const rewrite_mapper rewrite_escape_mapper;
|
||||
|
||||
const rewrite_mapper *
|
||||
rewrite_mapper_find(
|
||||
const char *name
|
||||
@ -538,6 +541,9 @@ rewrite_mapper_find(
|
||||
if ( !strcasecmp( name, "ldap" ))
|
||||
return &rewrite_ldap_mapper;
|
||||
|
||||
if ( !strcasecmp( name, "escape" ))
|
||||
return &rewrite_escape_mapper;
|
||||
|
||||
for (i=0; i<num_mappers; i++)
|
||||
if ( !strcasecmp( name, mappers[i]->rm_name ))
|
||||
return mappers[i];
|
||||
|
23
tests/data/rewrite.conf
Normal file
23
tests/data/rewrite.conf
Normal file
@ -0,0 +1,23 @@
|
||||
rewriteEngine on
|
||||
|
||||
# We have an attribute value from a DN that we want to paste into a filter
|
||||
#
|
||||
# N.B. Do not use ${escape2filter(${unescapedn($1)})}, but chain them inside
|
||||
# the same rewriteMap like this, since only that is safe in the presence of
|
||||
# arbitrary data
|
||||
rewriteMap escape dn2filter unescapedn escape2filter
|
||||
|
||||
# We have a DN that we want to paste into a filter
|
||||
rewriteMap escape dn2dnfilter escape2filter
|
||||
|
||||
# We have a filter value and want to construct a DN
|
||||
rewriteMap escape fitler2dn unescapefilter escape2dn
|
||||
|
||||
rewriteContext testdn2filter
|
||||
rewriteRule "^([^=]*)=([^,+]*)((\+[^,]*)?,(.*))?" "(&($1=${dn2filter($2)})(entryDN:dnOneLevelMatch:=$5))" ":"
|
||||
|
||||
rewriteContext testdn2dnfilter
|
||||
rewriteRule ".*" "entryDN=${dn2dnfilter($0)}" ":"
|
||||
|
||||
rewriteContext testfilter2dn
|
||||
rewriteRule "^\(([^=]*)=([^)]*)\)" "$1=${fitler2dn($2)},dc=example,dc=com" ":"
|
20
tests/data/rewrite.out
Normal file
20
tests/data/rewrite.out
Normal file
@ -0,0 +1,20 @@
|
||||
# making a filter from a DN
|
||||
uid=test\20\2c\31\\ -> (&(uid=test ,1\5C)(entryDN:dnOneLevelMatch:=)) [0:ok]
|
||||
uid=test,ou=People,dc=example,dc=com -> (&(uid=test)(entryDN:dnOneLevelMatch:=ou=People,dc=example,dc=com)) [0:ok]
|
||||
cn=test\00)(uid=test,dc=example,dc=com -> (&(cn=test\00\29\28uid=test)(entryDN:dnOneLevelMatch:=dc=example,dc=com)) [0:ok]
|
||||
cn=* -> (&(cn=\2A)(entryDN:dnOneLevelMatch:=)) [0:ok]
|
||||
cn=*\\ -> (&(cn=\2A\5C)(entryDN:dnOneLevelMatch:=)) [0:ok]
|
||||
cn=*\ -> (null) [-1:error]
|
||||
|
||||
# pasting a DN into a filter
|
||||
uid=test\20\31\\ -> entryDN=uid=test\5C20\5C31\5C\5C [0:ok]
|
||||
cn=test)(uid=test,dc=example,dc=com -> entryDN=cn=test\29\28uid=test,dc=example,dc=com [0:ok]
|
||||
cn=* -> entryDN=cn=\2A [0:ok]
|
||||
cn=*\\ -> entryDN=cn=\2A\5C\5C [0:ok]
|
||||
cn=*\ -> entryDN=cn=\2A\5C [0:ok]
|
||||
|
||||
# pasting a filter into a DN
|
||||
(uid=test) -> uid=test,dc=example,dc=com [0:ok]
|
||||
(cn=something ,\29+\28 \2A=) -> cn=something \2C)\2B( *\3D,dc=example,dc=com [0:ok]
|
||||
(description=test\20\31*) -> (null) [-1:error]
|
||||
(description=test\20\31) -> description=test 1,dc=example,dc=com [0:ok]
|
89
tests/scripts/test087-librewrite
Executable file
89
tests/scripts/test087-librewrite
Executable file
@ -0,0 +1,89 @@
|
||||
#! /bin/sh
|
||||
# $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>.
|
||||
|
||||
echo "running defines.sh"
|
||||
. $SRCDIR/scripts/defines.sh
|
||||
|
||||
if test $RWM = rwmno ; then
|
||||
echo "rwm (Rewrite/remap) overlay not available, test skipped"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "" >>$TESTOUT
|
||||
echo "# making a filter from a DN" > $TESTOUT
|
||||
echo "Testing DN unescaping then escaping for use in a filter..."
|
||||
for input in \
|
||||
"uid=test\\20\\2c\\31\\\\" \
|
||||
"uid=test,ou=People,dc=example,dc=com" \
|
||||
"cn=test\\00)(uid=test,dc=example,dc=com" \
|
||||
"cn=*" \
|
||||
"cn=*\\\\" \
|
||||
"cn=*\\" \
|
||||
; do
|
||||
|
||||
$TESTWD/../libraries/librewrite/rewrite -f $DATADIR/rewrite.conf \
|
||||
-r testdn2filter "$input" >>$TESTOUT 2>/dev/null
|
||||
if test $? != 0 ; then
|
||||
echo "rewriting failed"
|
||||
exit $?
|
||||
fi
|
||||
done
|
||||
|
||||
echo "" >>$TESTOUT
|
||||
echo "# pasting a DN into a filter" >> $TESTOUT
|
||||
echo "Testing filter escaping..."
|
||||
for input in \
|
||||
"uid=test\\20\\31\\\\" \
|
||||
"cn=test)(uid=test,dc=example,dc=com" \
|
||||
"cn=*" \
|
||||
"cn=*\\\\" \
|
||||
"cn=*\\" \
|
||||
; do
|
||||
|
||||
$TESTWD/../libraries/librewrite/rewrite -f $DATADIR/rewrite.conf \
|
||||
-r testdn2dnfilter "$input" >>$TESTOUT 2>/dev/null
|
||||
if test $? != 0 ; then
|
||||
echo "rewriting failed"
|
||||
exit $?
|
||||
fi
|
||||
done
|
||||
|
||||
echo "" >>$TESTOUT
|
||||
echo "# pasting a filter into a DN" >> $TESTOUT
|
||||
echo "Testing filter unescaping then escaping the value into a DN..."
|
||||
for input in \
|
||||
"(uid=test)" \
|
||||
"(cn=something ,\\29+\\28 \\2A=)" \
|
||||
"(description=test\\20\\31*)" \
|
||||
"(description=test\\20\\31)" \
|
||||
; do
|
||||
|
||||
$TESTWD/../libraries/librewrite/rewrite -f $DATADIR/rewrite.conf \
|
||||
-r testfilter2dn "$input" >>$TESTOUT 2>/dev/null
|
||||
if test $? != 0 ; then
|
||||
echo "rewriting failed"
|
||||
exit $?
|
||||
fi
|
||||
done
|
||||
|
||||
$CMP $DATADIR/rewrite.out $TESTOUT > $CMPOUT
|
||||
|
||||
if test $? != 0 ; then
|
||||
echo "comparison failed - rewriting did not complete correctly"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ">>>>> Test succeeded"
|
||||
exit 0
|
Loading…
Reference in New Issue
Block a user