Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
orbea -
OpenLDAP
Commits
8da6bf19
Commit
8da6bf19
authored
Apr 17, 2004
by
Howard Chu
Browse files
Added referential integrity and attribute uniqueness overlays
parent
71921f21
Changes
5
Hide whitespace changes
Inline
Side-by-side
doc/man/man5/slapo-refint.5
0 → 100644
View file @
8da6bf19
.TH SLAPO-REFINT 5 "RELEASEDATE" "OpenLDAP LDVERSION"
.\" Copyright 2004 The OpenLDAP Foundation All Rights Reserved.
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
.\" $OpenLDAP$
.SH NAME
slapo-refint \- Referential Integrity overlay
.SH SYNOPSIS
ETCDIR/slapd.conf
.SH DESCRIPTION
The Referential Integrity overlay can be used with a backend database such as
.BR slapd-bdb (5)
to maintain the cohesiveness of a schema which utilizes reference attributes.
.LP
Integrity is maintained by updating database records which contain the named
attributes to match the results of a
.B modrdn
or
.B delete
operation. For example, if the integrity attribute were configured as
.B manager ,
deletion of the record "uid=robert,ou=people,o=openldap.org" would trigger a
search for all other records which have a
.B manager
attribute containing that DN. Entries matching that search would have their
.B manager
attribute removed.
.SH CONFIGURATION
These
.B slapd.conf
options apply to the Referential Integrity overlay.
They should appear after the
.B overlay
directive and before any subsequent
.B database
directive.
.TP
.B refint_attributes <attribute...>
Specify one or more attributes which for which integrity will be maintained
as described above.
.TP
.B refint_nothing <string>
Specify an arbitrary value to be used as a placeholder when the last value
would otherwise be deleted from an attribute. This can be useful in cases
where the schema requires the existence of an attribute for which referential
integrity is enforced. The attempted deletion of a required attribute will
otherwise result in an Object Class Violation, causing the request to fail.
.B
.SH FILES
.TP
ETCDIR/slapd.conf
default slapd configuration file
.SH SEE ALSO
.BR slapd.conf (5).
doc/man/man5/slapo-unique.5
0 → 100644
View file @
8da6bf19
.TH SLAPO-UNIQUE 5 "RELEASEDATE" "OpenLDAP LDVERSION"
.\" Copyright 2004 The OpenLDAP Foundation All Rights Reserved.
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
.\" $OpenLDAP$
.SH NAME
slapo-unique \- Attribute Uniqueness overlay
.SH SYNOPSIS
ETCDIR/slapd.conf
.SH DESCRIPTION
The Attribute Uniqueness overlay can be used with a backend database such as
.BR slapd-bdb (5)
to enforce the uniqueness of some or all attributes within a subtree. This
subtree defaults to the base DN of the database for which the Uniqueness
overlay is configured.
.LP
Uniqueness is enforced by searching the subtree to ensure that the values of
all attributes presented with an
.B add ,
.B modify
or
.B modrdn
operation are unique within the subtree.
For example, if uniquness were enforced for the
.B uid
attribute, the subtree would be searched for any other records which also
have a
.B uid
attribute containing the same value. If any are found, the request is
rejected.
.SH CONFIGURATION
These
.B slapd.conf
options apply to the Attribute Uniqueness overlay.
They should appear after the
.B overlay
directive and before any subsequent
.B database
directive.
.TP
.B unique_base <basedn>
Configure the subtree against which uniqueness searches will be invoked.
The
.B basedn
defaults to the base DN of the database for which uniqueness is configured.
.TP
.B unique_ignore <attribute...>
Configure one or more attributes for which uniqueness will not be enforced.
If not configured, all non-operational (eg, system) attributes must be
unique. Note that the
.B unique_ignore
list should generally contain the
.B objectClass ,
.B dc ,
.B ou
and
.B o
attributes, as these will generally not be unique, nor are they operational
attributes.
.TP
.B unique_attributes <attribute...>
Specify one or more attributes which for which uniqueness will be enforced.
If not specified, all attributes which are not operational (eg, system
attributes such as
.B entryUUID )
or specified via the
.B unique_ignore
directive above must be unique within the subtree.
.TP
.B unique_strict
By default, uniqueness is not enforced for null values. Enabling
.B unique_strict
mode extends the concept of uniqueness to include null values, such that
only one attribute within a subtree will be allowed to have a null value.
.SH CAVEATS
.LP
The search key is generated with attributes that are non-operational, not
on the
.B unique_ignore
list, and included in the
.B unique_attributes
list, in that order. This makes it possible to create interesting and
unusable configurations.
.LP
Typical attributes for the
.B unique_ignore
directive are intentionally not hardcoded into the overlay to allow for
maximum flexibility in meeting site-specific requirements.
.SH FILES
.TP
ETCDIR/slapd.conf
default slapd configuration file
.SH SEE ALSO
.BR slapd.conf (5).
servers/slapd/overlays/Makefile.in
View file @
8da6bf19
...
...
@@ -19,6 +19,8 @@ SRCS = overlays.c \
dyngroup.c
\
lastmod.c
\
pcache.c
\
refint.c
\
unique.c
\
rwm.c rwmconf.c rwmdn.c rwmmap.c
OBJS
=
overlays.lo
\
chain.lo
\
...
...
@@ -26,6 +28,8 @@ OBJS = overlays.lo \
dyngroup.lo
\
lastmod.lo
\
pcache.lo
\
refint.lo
\
unique.lo
\
rwm.lo rwmconf.lo rwmdn.lo rwmmap.lo
LDAP_INCDIR
=
../../../include
...
...
@@ -58,6 +62,12 @@ lastmod.la : lastmod.lo $(@PLAT@_LINK_LIBS)
pcache.la
:
pcache.lo $(@PLAT@_LINK_LIBS)
$(LTLINK_MOD)
-module
-o
$@
pcache.lo version.lo
$(LINK_LIBS)
refint.la
:
refint.lo $(@PLAT@_LINK_LIBS)
$(LTLINK_MOD)
-module
-o
$@
refint.lo version.lo
$(LINK_LIBS)
unique.la
:
unique.lo $(@PLAT@_LINK_LIBS)
$(LTLINK_MOD)
-module
-o
$@
unique.lo version.lo
$(LINK_LIBS)
rwm.la
:
rwm.lo $(@PLAT@_LINK_LIBS)
$(LTLINK_MOD)
-module
-o
$@
rwm.lo rwmconf.lo rwmdn.lo rwmmap.lo version.lo
$(LINK_LIBS)
...
...
servers/slapd/overlays/refint.c
0 → 100644
View file @
8da6bf19
/* refint.c - referential integrity module */
/* $OpenLDAP$ */
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
*
* Copyright 2004 The OpenLDAP Foundation.
* Portions Copyright 2004 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 the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
/* ACKNOWLEDGEMENTS:
* This work was initially developed by Symas Corp. for inclusion in
* OpenLDAP Software. This work was sponsored by Hewlett-Packard.
*/
#include
"portable.h"
/* This module maintains referential integrity for a set of
* DN-valued attributes by searching for all references to a given
* DN whenever the DN is changed or its entry is deleted, and making
* the appropriate update.
*
* Updates are performed using the database rootdn, but the ModifiersName
* is always set to refint_dn.
*/
#ifdef SLAPD_OVER_REFINT
#include
<stdio.h>
#include
<ac/string.h>
#include
<ac/socket.h>
#include
"slap.h"
static
slap_overinst
refint
;
/* The DN to use in the ModifiersName for all refint updates */
static
BerValue
refint_dn
=
BER_BVC
(
"cn=Referential Integrity Overlay"
);
typedef
struct
refint_attrs_s
{
struct
refint_attrs_s
*
next
;
AttributeDescription
*
attr
;
}
refint_attrs
;
typedef
struct
dependents_s
{
struct
dependents_s
*
next
;
BerValue
dn
;
/* target dn */
Modifications
*
mm
;
}
dependent_data
;
typedef
struct
refint_data_s
{
const
char
*
message
;
/* breadcrumbs */
struct
refint_attrs_s
*
attrs
;
/* list of known attrs */
struct
dependents_s
*
mods
;
/* modifications returned from callback */
BerValue
dn
;
/* basedn in parent, searchdn in call */
BerValue
newdn
;
/* replacement value for modrdn callback */
BerValue
nnewdn
;
/* normalized replacement value */
BerValue
nothing
;
/* the nothing value, if needed */
BerValue
nnothing
;
/* normalized nothingness */
}
refint_data
;
/*
** allocate new refint_data;
** initialize, copy basedn;
** store in on_bi.bi_private;
**
*/
static
int
refint_db_init
(
BackendDB
*
be
)
{
slap_overinst
*
on
=
(
slap_overinst
*
)
be
->
bd_info
;
refint_data
*
id
=
ch_malloc
(
sizeof
(
refint_data
));
refint_attrs
*
ip
;
id
->
message
=
"_init"
;
id
->
attrs
=
NULL
;
id
->
newdn
.
bv_val
=
NULL
;
id
->
nothing
.
bv_val
=
NULL
;
id
->
nnothing
.
bv_val
=
NULL
;
ber_dupbv
(
&
id
->
dn
,
&
be
->
be_nsuffix
[
0
]
);
on
->
on_bi
.
bi_private
=
id
;
return
(
0
);
}
/*
** if command = attributes:
** foreach argument:
** convert to attribute;
** add to configured attribute list;
** elseif command = basedn:
** set our basedn to argument;
**
*/
static
int
refint_config
(
BackendDB
*
be
,
const
char
*
fname
,
int
lineno
,
int
argc
,
char
**
argv
)
{
slap_overinst
*
on
=
(
slap_overinst
*
)
be
->
bd_info
;
refint_data
*
id
=
on
->
on_bi
.
bi_private
;
refint_attrs
*
ip
;
const
char
*
text
;
AttributeDescription
*
ad
;
BerValue
dn
;
int
i
;
if
(
!
strcasecmp
(
*
argv
,
"refint_attributes"
))
{
for
(
i
=
1
;
i
<
argc
;
i
++
)
{
for
(
ip
=
id
->
attrs
;
ip
;
ip
=
ip
->
next
)
if
(
!
strcmp
(
argv
[
i
],
ip
->
attr
->
ad_cname
.
bv_val
))
{
Debug
(
LDAP_DEBUG_ANY
,
"%s: line %d: duplicate attribute <s>, ignored
\n
"
,
fname
,
lineno
,
argv
[
i
]);
continue
;
}
ad
=
NULL
;
if
(
slap_str2ad
(
argv
[
i
],
&
ad
,
&
text
)
!=
LDAP_SUCCESS
)
{
Debug
(
LDAP_DEBUG_ANY
,
"%s: line %d: bad attribute <%s>, ignored
\n
"
,
fname
,
lineno
,
text
);
continue
;
/* XXX */
}
else
if
(
ad
->
ad_next
)
{
Debug
(
LDAP_DEBUG_ANY
,
"%s: line %d: multiple attributes match <%s>, ignored
\n
"
,
fname
,
lineno
,
argv
[
i
]);
continue
;
}
ip
=
ch_malloc
(
sizeof
(
refint_attrs
));
ip
->
attr
=
ad
;
ip
->
next
=
id
->
attrs
;
id
->
attrs
=
ip
;
Debug
(
LDAP_DEBUG_ANY
,
"%s: line %d: new attribute <%s>
\n
"
,
fname
,
lineno
,
argv
[
i
]);
}
}
else
if
(
!
strcasecmp
(
*
argv
,
"refint_base"
))
{
/* XXX only one basedn (yet) - need validate argument! */
if
(
id
->
dn
.
bv_val
)
ch_free
(
id
->
dn
.
bv_val
);
ber_str2bv
(
argv
[
1
],
0
,
0
,
&
dn
);
Debug
(
LDAP_DEBUG_ANY
,
"%s: line %d: new baseDN <%s>
\n
"
,
fname
,
lineno
,
argv
[
1
]);
if
(
dnNormalize
(
0
,
NULL
,
NULL
,
&
dn
,
&
id
->
dn
,
NULL
))
{
Debug
(
LDAP_DEBUG_ANY
,
"%s: line %d: bad baseDN!
\n
"
,
fname
,
lineno
,
0
);
return
(
1
);
}
}
else
if
(
!
strcasecmp
(
*
argv
,
"refint_nothing"
))
{
if
(
id
->
nothing
.
bv_val
)
ch_free
(
id
->
nothing
.
bv_val
);
if
(
id
->
nnothing
.
bv_val
)
ch_free
(
id
->
nnothing
.
bv_val
);
ber_str2bv
(
argv
[
1
],
0
,
1
,
&
id
->
nothing
);
if
(
dnNormalize
(
0
,
NULL
,
NULL
,
&
id
->
nothing
,
&
id
->
nnothing
,
NULL
))
{
Debug
(
LDAP_DEBUG_ANY
,
"%s: line %d: bad nothingDN!
\n
"
,
fname
,
lineno
,
0
);
return
(
1
);
}
Debug
(
LDAP_DEBUG_ANY
,
"%s: line %d: new nothingDN<%s>
\n
"
,
fname
,
lineno
,
argv
[
1
]);
}
else
{
return
(
SLAP_CONF_UNKNOWN
);
}
id
->
message
=
"_config"
;
return
(
0
);
}
/*
** nothing really happens here;
**
*/
static
int
refint_open
(
BackendDB
*
be
)
{
slap_overinst
*
on
=
(
slap_overinst
*
)
be
->
bd_info
;
refint_data
*
id
=
on
->
on_bi
.
bi_private
;
id
->
message
=
"_open"
;
return
(
0
);
}
/*
** foreach configured attribute:
** free it;
** free our basedn;
** (do not) free id->message;
** reset on_bi.bi_private;
** free our config data;
**
*/
static
int
refint_close
(
BackendDB
*
be
)
{
slap_overinst
*
on
=
(
slap_overinst
*
)
be
->
bd_info
;
refint_data
*
id
=
on
->
on_bi
.
bi_private
;
refint_attrs
*
ii
,
*
ij
;
id
->
message
=
"_close"
;
for
(
ii
=
id
->
attrs
;
ii
;
ii
=
ij
)
{
ij
=
ii
->
next
;
ch_free
(
ii
);
}
ch_free
(
id
->
dn
.
bv_val
);
ch_free
(
id
->
nothing
.
bv_val
);
ch_free
(
id
->
nnothing
.
bv_val
);
on
->
on_bi
.
bi_private
=
NULL
;
/* XXX */
ch_free
(
id
);
return
(
0
);
}
/*
** delete callback
** generates a list of Modification* from search results
*/
static
int
refint_delete_cb
(
Operation
*
op
,
SlapReply
*
rs
)
{
Attribute
*
a
;
BerVarray
b
=
NULL
;
refint_data
*
id
,
*
dd
=
op
->
o_callback
->
sc_private
;
refint_attrs
*
ia
,
*
da
=
dd
->
attrs
;
dependent_data
*
ip
,
*
dp
=
NULL
;
Modifications
*
mp
,
*
ma
;
int
i
;
Debug
(
LDAP_DEBUG_TRACE
,
"refint_delete_cb <%s>
\n
"
,
rs
->
sr_entry
?
rs
->
sr_entry
->
e_name
.
bv_val
:
"NOTHING"
,
0
,
0
);
if
(
rs
->
sr_type
!=
REP_SEARCH
||
!
rs
->
sr_entry
)
return
(
0
);
dd
->
message
=
"_delete_cb"
;
/*
** foreach configured attribute type:
** if this attr exists in the search result,
** and it has a value matching the target:
** allocate a Modification;
** allocate its array of 2 BerValues;
** if only one value, and we have a configured Nothing:
** allocate additional Modification
** type = MOD_ADD
** BerValues[] = { Nothing, NULL };
** add to list
** type = MOD_DELETE
** BerValues[] = { our target dn, NULL };
** add this mod to the list of mods;
**
*/
ip
=
ch_malloc
(
sizeof
(
dependent_data
));
ip
->
dn
.
bv_val
=
NULL
;
ip
->
next
=
NULL
;
ip
->
mm
=
NULL
;
ma
=
NULL
;
for
(
ia
=
da
;
ia
;
ia
=
ia
->
next
)
{
if
(
a
=
attr_find
(
rs
->
sr_entry
->
e_attrs
,
ia
->
attr
))
for
(
i
=
0
,
b
=
a
->
a_nvals
;
b
[
i
].
bv_val
;
i
++
)
if
(
bvmatch
(
&
dd
->
dn
,
&
b
[
i
]))
{
if
(
!
ip
->
dn
.
bv_val
)
ber_dupbv
(
&
ip
->
dn
,
&
rs
->
sr_entry
->
e_nname
);
if
(
!
b
[
1
].
bv_val
&&
dd
->
nothing
.
bv_val
)
{
mp
=
ch_malloc
(
sizeof
(
Modifications
));
mp
->
sml_desc
=
ia
->
attr
;
/* XXX */
mp
->
sml_type
=
a
->
a_desc
->
ad_cname
;
mp
->
sml_values
=
ch_malloc
(
2
*
sizeof
(
BerValue
));
mp
->
sml_nvalues
=
ch_malloc
(
2
*
sizeof
(
BerValue
));
mp
->
sml_values
[
1
].
bv_len
=
mp
->
sml_nvalues
[
1
].
bv_len
=
0
;
mp
->
sml_values
[
1
].
bv_val
=
mp
->
sml_nvalues
[
1
].
bv_val
=
NULL
;
mp
->
sml_op
=
LDAP_MOD_ADD
;
ber_dupbv
(
&
mp
->
sml_values
[
0
],
&
dd
->
nothing
);
ber_dupbv
(
&
mp
->
sml_nvalues
[
0
],
&
dd
->
nnothing
);
mp
->
sml_next
=
ma
;
ma
=
mp
;
}
/* this might violate the object class */
mp
=
ch_malloc
(
sizeof
(
Modifications
));
mp
->
sml_desc
=
ia
->
attr
;
/* XXX */
mp
->
sml_type
=
a
->
a_desc
->
ad_cname
;
mp
->
sml_values
=
ch_malloc
(
2
*
sizeof
(
BerValue
));
mp
->
sml_nvalues
=
ch_malloc
(
2
*
sizeof
(
BerValue
));
mp
->
sml_values
[
1
].
bv_len
=
mp
->
sml_nvalues
[
1
].
bv_len
=
0
;
mp
->
sml_values
[
1
].
bv_val
=
mp
->
sml_nvalues
[
1
].
bv_val
=
NULL
;
mp
->
sml_op
=
LDAP_MOD_DELETE
;
ber_dupbv
(
&
mp
->
sml_values
[
0
],
&
dd
->
dn
);
ber_dupbv
(
&
mp
->
sml_nvalues
[
0
],
&
mp
->
sml_values
[
0
]);
mp
->
sml_next
=
ma
;
ma
=
mp
;
Debug
(
LDAP_DEBUG_TRACE
,
"refint_delete_cb: %s: %s
\n
"
,
a
->
a_desc
->
ad_cname
.
bv_val
,
dd
->
dn
.
bv_val
,
0
);
break
;
}
}
ip
->
mm
=
ma
;
ip
->
next
=
dd
->
mods
;
dd
->
mods
=
ip
;
return
(
0
);
}
/*
** null callback
** does nothing
*/
static
int
refint_null_cb
(
Operation
*
op
,
SlapReply
*
rs
)
{
((
refint_data
*
)
op
->
o_callback
->
sc_private
)
->
message
=
"_null_cb"
;
return
(
LDAP_SUCCESS
);
}
/*
** modrdn callback
** generates a list of Modification* from search results
*/
static
int
refint_modrdn_cb
(
Operation
*
op
,
SlapReply
*
rs
)
{
Attribute
*
a
;
BerVarray
b
=
NULL
;
refint_data
*
id
,
*
dd
=
op
->
o_callback
->
sc_private
;
refint_attrs
*
ia
,
*
da
=
dd
->
attrs
;
dependent_data
*
ip
=
NULL
,
*
dp
=
NULL
;
Modifications
*
mp
;
int
i
,
j
,
fix
;
Debug
(
LDAP_DEBUG_TRACE
,
"refint_modrdn_cb <%s>
\n
"
,
rs
->
sr_entry
?
rs
->
sr_entry
->
e_name
.
bv_val
:
"NOTHING"
,
0
,
0
);
if
(
rs
->
sr_type
!=
REP_SEARCH
||
!
rs
->
sr_entry
)
return
(
0
);
dd
->
message
=
"_modrdn_cb"
;
/*
** foreach configured attribute type:
** if this attr exists in the search result,
** and it has a value matching the target:
** allocate a pair of Modifications;
** make it MOD_ADD the new value and MOD_DELETE the old;
** allocate its array of BerValues;
** foreach value in the search result:
** if it matches our target value, replace it;
** otherwise, copy from the search result;
** terminate the array of BerValues;
** add these mods to the list of mods;
**
*/
for
(
ia
=
da
;
ia
;
ia
=
ia
->
next
)
{
if
(
a
=
attr_find
(
rs
->
sr_entry
->
e_attrs
,
ia
->
attr
))
{
for
(
fix
=
0
,
i
=
0
,
b
=
a
->
a_nvals
;
b
[
i
].
bv_val
;
i
++
)
if
(
bvmatch
(
&
dd
->
dn
,
&
b
[
i
]))
{
fix
++
;
break
;
}
if
(
fix
)
{
if
(
!
ip
)
{
ip
=
ch_malloc
(
sizeof
(
dependent_data
));
ip
->
next
=
NULL
;
ip
->
mm
=
NULL
;
ber_dupbv
(
&
ip
->
dn
,
&
rs
->
sr_entry
->
e_nname
);
}
mp
=
ch_malloc
(
sizeof
(
Modifications
));
mp
->
sml_op
=
LDAP_MOD_ADD
;
mp
->
sml_desc
=
ia
->
attr
;
/* XXX */
mp
->
sml_type
=
ia
->
attr
->
ad_cname
;
mp
->
sml_values
=
ch_malloc
(
2
*
sizeof
(
BerValue
));
mp
->
sml_nvalues
=
ch_malloc
(
2
*
sizeof
(
BerValue
));
ber_dupbv
(
&
mp
->
sml_values
[
0
],
&
dd
->
newdn
);
ber_dupbv
(
&
mp
->
sml_nvalues
[
0
],
&
dd
->
nnewdn
);
mp
->
sml_values
[
1
].
bv_len
=
mp
->
sml_nvalues
[
1
].
bv_len
=
0
;
mp
->
sml_values
[
1
].
bv_val
=
mp
->
sml_nvalues
[
1
].
bv_val
=
NULL
;
mp
->
sml_next
=
ip
->
mm
;
ip
->
mm
=
mp
;
mp
=
ch_malloc
(
sizeof
(
Modifications
));
mp
->
sml_op
=
LDAP_MOD_DELETE
;
mp
->
sml_desc
=
ia
->
attr
;
/* XXX */
mp
->
sml_type
=
ia
->
attr
->
ad_cname
;
mp
->
sml_values
=
ch_malloc
(
2
*
sizeof
(
BerValue
));
mp
->
sml_nvalues
=
ch_malloc
(
2
*
sizeof
(
BerValue
));
ber_dupbv
(
&
mp
->
sml_values
[
0
],
&
dd
->
dn
);
ber_dupbv
(
&
mp
->
sml_nvalues
[
0
],
&
dd
->
dn
);
mp
->
sml_values
[
1
].
bv_len
=
mp
->
sml_nvalues
[
1
].
bv_len
=
0
;
mp
->
sml_values
[
1
].
bv_val
=
mp
->
sml_nvalues
[
1
].
bv_val
=
NULL
;
mp
->
sml_next
=
ip
->
mm
;
ip
->
mm
=
mp
;
Debug
(
LDAP_DEBUG_TRACE
,
"refint_modrdn_cb: %s: %s
\n
"
,
a
->
a_desc
->
ad_cname
.
bv_val
,
dd
->
dn
.
bv_val
,
0
);
}
}
}
if
(
ip
)
{
ip
->
next
=
dd
->
mods
;
dd
->
mods
=
ip
;
}
return
(
0
);
}
/*
** refint_response
** search for matching records and modify them
*/
static
int
refint_response
(
Operation
*
op
,
SlapReply
*
rs
)
{
Operation
nop
=
*
op
;
SlapReply
nrs
=