.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
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
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
# 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
##
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
--- /dev/null
+/* $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
+};
/* 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
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];
--- /dev/null
+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" ":"
--- /dev/null
+# 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]
--- /dev/null
+#! /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