ITS#9817 Introduce DN and filter manipulation tools

This commit is contained in:
Ondřej Kuzník 2022-06-07 09:35:45 +01:00
parent 598929a507
commit 56877e333b
7 changed files with 402 additions and 4 deletions

View File

@ -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

View File

@ -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

View 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
};

View File

@ -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
View 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
View 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]

View 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