summaryrefslogtreecommitdiffstats
path: root/ldap/servers/plugins
diff options
context:
space:
mode:
authorcvsadm <cvsadm>2005-01-21 00:44:34 +0000
committercvsadm <cvsadm>2005-01-21 00:44:34 +0000
commitb2093e3016027d6b5cf06b3f91f30769bfc099e2 (patch)
treecf58939393a9032182c4fbc4441164a9456e82f8 /ldap/servers/plugins
downloadds-b2093e3016027d6b5cf06b3f91f30769bfc099e2.tar.gz
ds-b2093e3016027d6b5cf06b3f91f30769bfc099e2.tar.xz
ds-b2093e3016027d6b5cf06b3f91f30769bfc099e2.zip
Moving NSCP Directory Server from DirectoryBranch to TRUNK, initial drop. (foxworth)ldapserver7x
Diffstat (limited to 'ldap/servers/plugins')
-rw-r--r--ldap/servers/plugins/Makefile101
-rw-r--r--ldap/servers/plugins/acl/ACL-Notes215
-rw-r--r--ldap/servers/plugins/acl/Makefile96
-rw-r--r--ldap/servers/plugins/acl/acl.c4118
-rw-r--r--ldap/servers/plugins/acl/acl.h867
-rw-r--r--ldap/servers/plugins/acl/acl_ext.c968
-rw-r--r--ldap/servers/plugins/acl/aclanom.c536
-rw-r--r--ldap/servers/plugins/acl/acldllmain.c128
-rw-r--r--ldap/servers/plugins/acl/acleffectiverights.c674
-rw-r--r--ldap/servers/plugins/acl/aclgroup.c442
-rw-r--r--ldap/servers/plugins/acl/aclinit.c537
-rw-r--r--ldap/servers/plugins/acl/acllas.c3848
-rw-r--r--ldap/servers/plugins/acl/acllist.c940
-rw-r--r--ldap/servers/plugins/acl/aclparse.c1928
-rw-r--r--ldap/servers/plugins/acl/aclplugin.c355
-rw-r--r--ldap/servers/plugins/acl/aclproxy.c195
-rw-r--r--ldap/servers/plugins/acl/aclutil.c1475
-rw-r--r--ldap/servers/plugins/acl/libacl.def16
-rw-r--r--ldap/servers/plugins/chainingdb/Makefile93
-rw-r--r--ldap/servers/plugins/chainingdb/cb.h461
-rw-r--r--ldap/servers/plugins/chainingdb/cb_abandon.c50
-rw-r--r--ldap/servers/plugins/chainingdb/cb_acl.c60
-rw-r--r--ldap/servers/plugins/chainingdb/cb_add.c227
-rw-r--r--ldap/servers/plugins/chainingdb/cb_bind.c290
-rw-r--r--ldap/servers/plugins/chainingdb/cb_cleanup.c22
-rw-r--r--ldap/servers/plugins/chainingdb/cb_close.c67
-rw-r--r--ldap/servers/plugins/chainingdb/cb_compare.c217
-rw-r--r--ldap/servers/plugins/chainingdb/cb_config.c600
-rw-r--r--ldap/servers/plugins/chainingdb/cb_conn_stateless.c1132
-rw-r--r--ldap/servers/plugins/chainingdb/cb_controls.c289
-rw-r--r--ldap/servers/plugins/chainingdb/cb_debug.c23
-rw-r--r--ldap/servers/plugins/chainingdb/cb_delete.c203
-rw-r--r--ldap/servers/plugins/chainingdb/cb_init.c120
-rw-r--r--ldap/servers/plugins/chainingdb/cb_instance.c1871
-rw-r--r--ldap/servers/plugins/chainingdb/cb_modify.c244
-rw-r--r--ldap/servers/plugins/chainingdb/cb_modrdn.c239
-rw-r--r--ldap/servers/plugins/chainingdb/cb_monitor.c228
-rw-r--r--ldap/servers/plugins/chainingdb/cb_schema.c45
-rw-r--r--ldap/servers/plugins/chainingdb/cb_search.c698
-rw-r--r--ldap/servers/plugins/chainingdb/cb_size.c22
-rw-r--r--ldap/servers/plugins/chainingdb/cb_start.c43
-rw-r--r--ldap/servers/plugins/chainingdb/cb_temp.c15
-rw-r--r--ldap/servers/plugins/chainingdb/cb_test.c76
-rw-r--r--ldap/servers/plugins/chainingdb/cb_unbind.c23
-rw-r--r--ldap/servers/plugins/chainingdb/cb_utils.c375
-rw-r--r--ldap/servers/plugins/chainingdb/cbdllmain.c128
-rw-r--r--ldap/servers/plugins/chainingdb/libcb.def15
-rw-r--r--ldap/servers/plugins/collation/Makefile99
-rw-r--r--ldap/servers/plugins/collation/collate.c454
-rw-r--r--ldap/servers/plugins/collation/collate.h36
-rw-r--r--ldap/servers/plugins/collation/collation.def10
-rw-r--r--ldap/servers/plugins/collation/config.c178
-rw-r--r--ldap/servers/plugins/collation/config.h12
-rw-r--r--ldap/servers/plugins/collation/debug.c14
-rw-r--r--ldap/servers/plugins/collation/dllmain.c129
-rw-r--r--ldap/servers/plugins/collation/orfilter.c984
-rw-r--r--ldap/servers/plugins/collation/orfilter.h10
-rw-r--r--ldap/servers/plugins/cos/Makefile79
-rw-r--r--ldap/servers/plugins/cos/cos.c266
-rw-r--r--ldap/servers/plugins/cos/cos.def11
-rw-r--r--ldap/servers/plugins/cos/cos_cache.c3566
-rw-r--r--ldap/servers/plugins/cos/cos_cache.h19
-rw-r--r--ldap/servers/plugins/cos/dllmain.c96
-rw-r--r--ldap/servers/plugins/distrib/Makefile109
-rw-r--r--ldap/servers/plugins/distrib/Makefile.AIX44
-rw-r--r--ldap/servers/plugins/distrib/Makefile.BSDI30
-rw-r--r--ldap/servers/plugins/distrib/Makefile.HPUX27
-rw-r--r--ldap/servers/plugins/distrib/Makefile.HPUX6427
-rw-r--r--ldap/servers/plugins/distrib/Makefile.IRIX30
-rw-r--r--ldap/servers/plugins/distrib/Makefile.Linux30
-rw-r--r--ldap/servers/plugins/distrib/Makefile.OSF129
-rw-r--r--ldap/servers/plugins/distrib/Makefile.ReliantUNIX30
-rw-r--r--ldap/servers/plugins/distrib/Makefile.SOLARIS30
-rw-r--r--ldap/servers/plugins/distrib/Makefile.SOLARIS6430
-rw-r--r--ldap/servers/plugins/distrib/Makefile.SOLARISx8630
-rw-r--r--ldap/servers/plugins/distrib/Makefile.UnixWare30
-rw-r--r--ldap/servers/plugins/distrib/Makefile.UnixWareUDK30
-rw-r--r--ldap/servers/plugins/distrib/Makefile.WINNT38
-rw-r--r--ldap/servers/plugins/distrib/README23
-rw-r--r--ldap/servers/plugins/distrib/distrib.c222
-rw-r--r--ldap/servers/plugins/distrib/distrib.dsp116
-rw-r--r--ldap/servers/plugins/distrib/dllmain.c101
-rw-r--r--ldap/servers/plugins/distrib/libdistrib.def10
-rw-r--r--ldap/servers/plugins/http/Makefile80
-rw-r--r--ldap/servers/plugins/http/dllmain.c98
-rw-r--r--ldap/servers/plugins/http/http.def13
-rw-r--r--ldap/servers/plugins/http/http_client.c290
-rw-r--r--ldap/servers/plugins/http/http_client.h64
-rw-r--r--ldap/servers/plugins/http/http_impl.c1479
-rw-r--r--ldap/servers/plugins/http/http_impl.h25
-rw-r--r--ldap/servers/plugins/passthru/Makefile90
-rw-r--r--ldap/servers/plugins/passthru/PT-Notes30
-rw-r--r--ldap/servers/plugins/passthru/libpassthru.def14
-rw-r--r--ldap/servers/plugins/passthru/passthru.h131
-rw-r--r--ldap/servers/plugins/passthru/ptbind.c144
-rw-r--r--ldap/servers/plugins/passthru/ptconfig.c301
-rw-r--r--ldap/servers/plugins/passthru/ptconn.c420
-rw-r--r--ldap/servers/plugins/passthru/ptdebug.c23
-rw-r--r--ldap/servers/plugins/passthru/ptdllmain.c131
-rw-r--r--ldap/servers/plugins/passthru/ptpreop.c252
-rw-r--r--ldap/servers/plugins/passthru/ptutil.c111
-rw-r--r--ldap/servers/plugins/presence/Makefile85
-rw-r--r--ldap/servers/plugins/presence/dllmain.c96
-rw-r--r--ldap/servers/plugins/presence/images/aim-offline.gifbin0 -> 113 bytes
-rw-r--r--ldap/servers/plugins/presence/images/aim-online.gifbin0 -> 895 bytes
-rw-r--r--ldap/servers/plugins/presence/images/icq-disabled.gifbin0 -> 138 bytes
-rw-r--r--ldap/servers/plugins/presence/images/icq-offline.gifbin0 -> 198 bytes
-rw-r--r--ldap/servers/plugins/presence/images/icq-online.gifbin0 -> 198 bytes
-rw-r--r--ldap/servers/plugins/presence/images/yahoo-offline.gifbin0 -> 84 bytes
-rw-r--r--ldap/servers/plugins/presence/images/yahoo-online.gifbin0 -> 140 bytes
-rw-r--r--ldap/servers/plugins/presence/presence.c1204
-rw-r--r--ldap/servers/plugins/presence/presence.def11
-rw-r--r--ldap/servers/plugins/presence/presence.ldif44
-rw-r--r--ldap/servers/plugins/pwdstorage/Makefile115
-rw-r--r--ldap/servers/plugins/pwdstorage/clear_pwd.c27
-rw-r--r--ldap/servers/plugins/pwdstorage/crypt_pwd.c91
-rw-r--r--ldap/servers/plugins/pwdstorage/dllmain.c91
-rw-r--r--ldap/servers/plugins/pwdstorage/libpwdstorage.def24
-rw-r--r--ldap/servers/plugins/pwdstorage/md5.h63
-rw-r--r--ldap/servers/plugins/pwdstorage/md5c.c337
-rw-r--r--ldap/servers/plugins/pwdstorage/ns-mta-md5_pwd.bu405
-rw-r--r--ldap/servers/plugins/pwdstorage/ns-mta-md5_pwd.c81
-rw-r--r--ldap/servers/plugins/pwdstorage/pwd_init.c146
-rw-r--r--ldap/servers/plugins/pwdstorage/pwdstorage.h99
-rw-r--r--ldap/servers/plugins/pwdstorage/sha_pwd.c111
-rw-r--r--ldap/servers/plugins/pwdstorage/ssha_pwd.c112
-rw-r--r--ldap/servers/plugins/referint/Makefile72
-rw-r--r--ldap/servers/plugins/referint/dllmain.c95
-rw-r--r--ldap/servers/plugins/referint/referint.c808
-rw-r--r--ldap/servers/plugins/referint/referint.def12
-rw-r--r--ldap/servers/plugins/replication/Makefile152
-rw-r--r--ldap/servers/plugins/replication/cl4.h65
-rw-r--r--ldap/servers/plugins/replication/cl4_api.c797
-rw-r--r--ldap/servers/plugins/replication/cl4_api.h67
-rw-r--r--ldap/servers/plugins/replication/cl4_init.c349
-rw-r--r--ldap/servers/plugins/replication/cl5.h38
-rw-r--r--ldap/servers/plugins/replication/cl5_api.c6512
-rw-r--r--ldap/servers/plugins/replication/cl5_api.h478
-rw-r--r--ldap/servers/plugins/replication/cl5_clcache.c910
-rw-r--r--ldap/servers/plugins/replication/cl5_clcache.h22
-rw-r--r--ldap/servers/plugins/replication/cl5_config.c868
-rw-r--r--ldap/servers/plugins/replication/cl5_init.c77
-rw-r--r--ldap/servers/plugins/replication/cl5_test.c830
-rw-r--r--ldap/servers/plugins/replication/cl5_test.h21
-rw-r--r--ldap/servers/plugins/replication/csnpl.c328
-rw-r--r--ldap/servers/plugins/replication/csnpl.h23
-rw-r--r--ldap/servers/plugins/replication/dllmain.c91
-rw-r--r--ldap/servers/plugins/replication/legacy_consumer.c707
-rw-r--r--ldap/servers/plugins/replication/llist.c336
-rw-r--r--ldap/servers/plugins/replication/llist.h26
-rw-r--r--ldap/servers/plugins/replication/profile.c42
-rw-r--r--ldap/servers/plugins/replication/repl.h366
-rw-r--r--ldap/servers/plugins/replication/repl5.h480
-rw-r--r--ldap/servers/plugins/replication/repl5_agmt.c1766
-rw-r--r--ldap/servers/plugins/replication/repl5_agmtlist.c618
-rw-r--r--ldap/servers/plugins/replication/repl5_backoff.c232
-rw-r--r--ldap/servers/plugins/replication/repl5_connection.c1493
-rw-r--r--ldap/servers/plugins/replication/repl5_inc_protocol.c1759
-rw-r--r--ldap/servers/plugins/replication/repl5_init.c572
-rw-r--r--ldap/servers/plugins/replication/repl5_mtnode_ext.c194
-rw-r--r--ldap/servers/plugins/replication/repl5_plugins.c1416
-rw-r--r--ldap/servers/plugins/replication/repl5_prot_private.h66
-rw-r--r--ldap/servers/plugins/replication/repl5_protocol.c502
-rw-r--r--ldap/servers/plugins/replication/repl5_protocol_util.c468
-rw-r--r--ldap/servers/plugins/replication/repl5_replica.c3387
-rw-r--r--ldap/servers/plugins/replication/repl5_replica_config.c750
-rw-r--r--ldap/servers/plugins/replication/repl5_replica_dnhash.c189
-rw-r--r--ldap/servers/plugins/replication/repl5_replica_hash.c243
-rw-r--r--ldap/servers/plugins/replication/repl5_replsupplier.c166
-rw-r--r--ldap/servers/plugins/replication/repl5_ruv.c2022
-rw-r--r--ldap/servers/plugins/replication/repl5_ruv.h88
-rw-r--r--ldap/servers/plugins/replication/repl5_schedule.c742
-rw-r--r--ldap/servers/plugins/replication/repl5_tot_protocol.c372
-rw-r--r--ldap/servers/plugins/replication/repl5_total.c869
-rw-r--r--ldap/servers/plugins/replication/repl5_updatedn_list.c243
-rw-r--r--ldap/servers/plugins/replication/repl_add.c30
-rw-r--r--ldap/servers/plugins/replication/repl_bind.c48
-rw-r--r--ldap/servers/plugins/replication/repl_compare.c34
-rw-r--r--ldap/servers/plugins/replication/repl_connext.c91
-rw-r--r--ldap/servers/plugins/replication/repl_controls.c337
-rw-r--r--ldap/servers/plugins/replication/repl_delete.c26
-rw-r--r--ldap/servers/plugins/replication/repl_entry.c38
-rw-r--r--ldap/servers/plugins/replication/repl_ext.c113
-rw-r--r--ldap/servers/plugins/replication/repl_extop.c1134
-rw-r--r--ldap/servers/plugins/replication/repl_globals.c108
-rw-r--r--ldap/servers/plugins/replication/repl_helper.c85
-rw-r--r--ldap/servers/plugins/replication/repl_helper.h69
-rw-r--r--ldap/servers/plugins/replication/repl_init.c312
-rw-r--r--ldap/servers/plugins/replication/repl_modify.c29
-rw-r--r--ldap/servers/plugins/replication/repl_modrdn.c28
-rw-r--r--ldap/servers/plugins/replication/repl_monitor.c58
-rw-r--r--ldap/servers/plugins/replication/repl_objset.c524
-rw-r--r--ldap/servers/plugins/replication/repl_objset.h37
-rw-r--r--ldap/servers/plugins/replication/repl_opext.c97
-rw-r--r--ldap/servers/plugins/replication/repl_ops.c180
-rw-r--r--ldap/servers/plugins/replication/repl_rootdse.c79
-rw-r--r--ldap/servers/plugins/replication/repl_search.c25
-rw-r--r--ldap/servers/plugins/replication/repl_shared.h132
-rw-r--r--ldap/servers/plugins/replication/replication.def16
-rw-r--r--ldap/servers/plugins/replication/replutil.c1073
-rw-r--r--ldap/servers/plugins/replication/tests/dnp_sim.c1033
-rw-r--r--ldap/servers/plugins/replication/tests/dnp_sim2.c972
-rw-r--r--ldap/servers/plugins/replication/tests/dnp_sim3.c1489
-rwxr-xr-xldap/servers/plugins/replication/tests/makesim58
-rw-r--r--ldap/servers/plugins/replication/urp.c1282
-rw-r--r--ldap/servers/plugins/replication/urp.h45
-rw-r--r--ldap/servers/plugins/replication/urp_glue.c235
-rw-r--r--ldap/servers/plugins/replication/urp_tombstone.c210
-rw-r--r--ldap/servers/plugins/retrocl/Makefile135
-rw-r--r--ldap/servers/plugins/retrocl/dllmain.c90
-rw-r--r--ldap/servers/plugins/retrocl/linktest.c16
-rw-r--r--ldap/servers/plugins/retrocl/retrocl.c341
-rw-r--r--ldap/servers/plugins/retrocl/retrocl.def15
-rw-r--r--ldap/servers/plugins/retrocl/retrocl.h123
-rw-r--r--ldap/servers/plugins/retrocl/retrocl.txt107
-rw-r--r--ldap/servers/plugins/retrocl/retrocl_cn.c391
-rw-r--r--ldap/servers/plugins/retrocl/retrocl_create.c317
-rw-r--r--ldap/servers/plugins/retrocl/retrocl_po.c529
-rw-r--r--ldap/servers/plugins/retrocl/retrocl_rootdse.c64
-rw-r--r--ldap/servers/plugins/retrocl/retrocl_trim.c505
-rw-r--r--ldap/servers/plugins/rever/Makefile111
-rw-r--r--ldap/servers/plugins/rever/des.c465
-rw-r--r--ldap/servers/plugins/rever/dllmain.c91
-rw-r--r--ldap/servers/plugins/rever/libdes.def13
-rw-r--r--ldap/servers/plugins/rever/rever.c77
-rw-r--r--ldap/servers/plugins/rever/rever.h34
-rw-r--r--ldap/servers/plugins/roles/Makefile95
-rw-r--r--ldap/servers/plugins/roles/dllmain.c96
-rw-r--r--ldap/servers/plugins/roles/roles.def10
-rw-r--r--ldap/servers/plugins/roles/roles_cache.c2061
-rw-r--r--ldap/servers/plugins/roles/roles_cache.h52
-rw-r--r--ldap/servers/plugins/roles/roles_plugin.c254
-rw-r--r--ldap/servers/plugins/shared/Makefile56
-rw-r--r--ldap/servers/plugins/shared/plugin-utils.h77
-rw-r--r--ldap/servers/plugins/shared/utils.c467
-rw-r--r--ldap/servers/plugins/statechange/Makefile78
-rw-r--r--ldap/servers/plugins/statechange/dllmain.c96
-rw-r--r--ldap/servers/plugins/statechange/statechange.c450
-rw-r--r--ldap/servers/plugins/statechange/statechange.def10
-rw-r--r--ldap/servers/plugins/syntaxes/Makefile87
-rw-r--r--ldap/servers/plugins/syntaxes/bin.c201
-rw-r--r--ldap/servers/plugins/syntaxes/ces.c168
-rw-r--r--ldap/servers/plugins/syntaxes/cis.c298
-rw-r--r--ldap/servers/plugins/syntaxes/debug.c20
-rw-r--r--ldap/servers/plugins/syntaxes/dllmain.c133
-rw-r--r--ldap/servers/plugins/syntaxes/dn.c98
-rw-r--r--ldap/servers/plugins/syntaxes/int.c188
-rw-r--r--ldap/servers/plugins/syntaxes/libsyntax.def24
-rw-r--r--ldap/servers/plugins/syntaxes/phonetic.c461
-rw-r--r--ldap/servers/plugins/syntaxes/sicis.c139
-rw-r--r--ldap/servers/plugins/syntaxes/string.c612
-rw-r--r--ldap/servers/plugins/syntaxes/syntax.h42
-rw-r--r--ldap/servers/plugins/syntaxes/tel.c135
-rw-r--r--ldap/servers/plugins/syntaxes/value.c209
-rw-r--r--ldap/servers/plugins/uiduniq/7bit.c722
-rw-r--r--ldap/servers/plugins/uiduniq/Makefile99
-rw-r--r--ldap/servers/plugins/uiduniq/UID-Notes93
-rw-r--r--ldap/servers/plugins/uiduniq/libuiduniq.def15
-rw-r--r--ldap/servers/plugins/uiduniq/uid.c1073
-rw-r--r--ldap/servers/plugins/vattrsp_template/Makefile79
-rw-r--r--ldap/servers/plugins/vattrsp_template/dllmain.c96
-rw-r--r--ldap/servers/plugins/vattrsp_template/vattrsp.c397
-rw-r--r--ldap/servers/plugins/vattrsp_template/vattrsp.def10
-rw-r--r--ldap/servers/plugins/views/Makefile79
-rw-r--r--ldap/servers/plugins/views/dllmain.c96
-rw-r--r--ldap/servers/plugins/views/views.c1779
-rw-r--r--ldap/servers/plugins/views/views.def10
267 files changed, 96477 insertions, 0 deletions
diff --git a/ldap/servers/plugins/Makefile b/ldap/servers/plugins/Makefile
new file mode 100644
index 00000000..051f3b70
--- /dev/null
+++ b/ldap/servers/plugins/Makefile
@@ -0,0 +1,101 @@
+#
+# BEGIN COPYRIGHT BLOCK
+# Copyright 2001 Sun Microsystems, Inc.
+# Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+# All rights reserved.
+# END COPYRIGHT BLOCK
+#
+#
+# GNU Makefile for Directory Server and LDAP SDK libraries
+#
+
+MCOM_ROOT = ../../../..
+LDAP_SRC = $(MCOM_ROOT)/ldapserver/ldap
+
+NOSTDCLEAN=true # don't let nsconfig.mk define target clean
+NOSTDSTRIP=true # don't let nsconfig.mk define target strip
+NSPR20=true # probably should be defined somewhere else (not sure where)
+
+include $(MCOM_ROOT)/ldapserver/nsconfig.mk
+include $(LDAP_SRC)/nsldap.mk
+
+all: _referint _collation _syntaxes _passthru _utils _uiduniq _roles _acl _replication _cos _pwdstorage _rever _chainingdb _distrib _retrocl _statechange _http _presence _views
+
+_utils:
+ cd shared; $(MAKE) $(MFLAGS) all
+_rever:
+ cd rever; $(MAKE) $(MFLAGS) all
+
+_chainingdb:
+ cd chainingdb; $(MAKE) $(MFLAGS) all
+
+_referint:
+ cd referint; $(MAKE) $(MFLAGS) all
+
+_collation:
+ cd collation; $(MAKE) $(MFLAGS) all
+
+_syntaxes:
+ cd syntaxes; $(MAKE) $(MFLAGS) all
+
+_passthru:
+ cd passthru; $(MAKE) $(MFLAGS) all
+
+_uiduniq:
+ cd uiduniq; $(MAKE) $(MFLAGS) all
+
+_replication:
+ cd replication; $(MAKE) $(MFLAGS) all
+
+_acl:
+ cd acl; $(MAKE) $(MFLAGS) all
+
+_pwdstorage:
+ cd pwdstorage; $(MAKE) $(MFLAGS) all
+
+_distrib:
+ cd distrib; $(MAKE) $(MFLAGS) all
+
+_roles:
+ cd roles; $(MAKE) $(MFLAGS) all
+
+_cos:
+ cd cos; $(MAKE) $(MFLAGS) all
+
+_statechange:
+ cd statechange; $(MAKE) $(MFLAGS) all
+
+_retrocl:
+ cd retrocl; $(MAKE) $(MFLAGS) all
+
+_http:
+ cd http; $(MAKE) $(MFLAGS) all
+
+_presence:
+ cd presence; $(MAKE) $(MFLAGS) all
+
+_views:
+ cd views; $(MAKE) $(MFLAGS) all
+
+clean:
+ cd rever; $(MAKE) $(MFLAGS) clean
+ cd referint; $(MAKE) $(MFLAGS) clean
+ cd collation; $(MAKE) $(MFLAGS) clean
+ cd syntaxes; $(MAKE) $(MFLAGS) clean
+ cd passthru; $(MAKE) $(MFLAGS) clean
+ cd shared; $(MAKE) $(MFLAGS) clean
+ cd uiduniq; $(MAKE) $(MFLAGS) clean
+ cd replication; $(MAKE) $(MFLAGS) clean
+ cd acl; $(MAKE) $(MFLAGS) clean
+ cd cos; $(MAKE) $(MFLAGS) clean
+ cd pwdstorage; $(MAKE) $(MFLAGS) clean
+ cd roles; $(MAKE) $(MFLAGS) clean
+ cd chainingdb; $(MAKE) $(MFLAGS) clean
+ cd distrib; $(MAKE) $(MFLAGS) clean
+ cd retrocl; $(MAKE) $(MFLAGS) clean
+ cd statechange; $(MAKE) $(MFLAGS) clean
+ cd presence; $(MAKE) $(MFLAGS) clean
+ cd http; $(MAKE) $(MFLAGS) clean
+ cd views; $(MAKE) $(MFLAGS) clean
+
+veryclean: clean
diff --git a/ldap/servers/plugins/acl/ACL-Notes b/ldap/servers/plugins/acl/ACL-Notes
new file mode 100644
index 00000000..e275c967
--- /dev/null
+++ b/ldap/servers/plugins/acl/ACL-Notes
@@ -0,0 +1,215 @@
+#
+# BEGIN COPYRIGHT BLOCK
+# Copyright 2001 Sun Microsystems, Inc.
+# Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+# All rights reserved.
+# END COPYRIGHT BLOCK
+#
+
+
+Date What ?
+===================================
+10/15/98 - Created the ACL plugin
+ - Created a new file aclplugin.c and split the old
+ acl.c to acl.c & aclparse.c files.
+ - Merged changes made upt 4.0B2
+10/21/98 - Added USERATTR rule.
+
+
+02/01/99 - Cleanup needed to be done in 5.0 to make it a real plugin
+=====================================================================================
+1. Do not use slap.h but use slapi-plugin.h. This will require
+ some work. Work involves
+ 1) Making the ACLCB an extensible object of CONN struct
+ 2) Remove reference of Connection & operation struct
+ 3) Need slapi plugin apis to get the IP and DNS so that
+ we can evaluate it in the LASes.
+ 4) Need new option to get values of conn , op & pb stuct like
+ cert, authtype,
+
+2. Make ACLPB hang from the Operation struct instead of the PBlock.
+3. Make ACLCB an extensible object of CONN struct and remove any reference
+ about acl private info.
+
+4. I implemented the Userattr rule before even deciding if we need in 5.0
+ or not. I think it is useful. The documents those were based on are
+ in http://jazz/users/prasanta/acl_manage_filter
+
+5. Move acllas_dn_parent to the libslapd. This is duplicated code and is
+ BAAAD.
+
+6. Use the new normalized dn code so that we don't have to it over and over again.
+ We have to very careful ins slapi_access_allowed() as we keep the dn around and
+ free it later ( we can use dn by ref ).
+
+7. Merge from DS4.1 ( proxy auth) to DS 5.0.
+
+8. Miscs
+ a) can we use the SDK URL parsing code ?
+ b) Merge teh printing routines ( it's all over ).
+
+My estimate for doing the above cleanup will require anywhere between 5 to 8 days.
+Run the ACL tests after all the changes -- that is a MUST.
+===============================
+04/28/99
+
+ -- All the work descibed above is done.
+ -- Also
+ a) Created a Pool pf ACLPB one of which is grabed at the init time.
+ b) Created a global lockarary which takes care of the concurreny issue between
+ aclpb & aclcb
+ c) Fixed plugin init.
+
+
+I think the userattr rule should be made generic
+
+ useAttr = "attrName#Type"
+
+ <Type> :== DN | GROUP | ROLE | URL | <value>
+ <value> :== < any printable String>
+
+Example:
+ userAttr = "manager#DN" --- similar to userdnattr
+ userAttr = "owner#GROUP" --- similar to groupdnattr
+ userAttr = "attr#ROLE" --- The value of attr contains a role definition
+ userAttr = "myattr#URL" --- The value contains a URL or filter
+ userAttr = "OU#Directory Server"
+ --- In this case the client's OU and the
+ resource entry's OU must have
+ "Directory Server" value.
+
+ This way we can get rid of userdnattr and groupdnattr and accomplish a
+ lot with a single rule.
+
+At this point, we are done with the changes and waiting for what needs to be
+done in 5.0.
+=================================
+06/01/1999
+ -- Split the code into smaller modules
+ ( aclanom, aclgroup, aclinit, ...)
+ --- The ACLs are read and kept in a AVL tree.
+ --- Few bugs fixed in the acl_scan_match code.
+
+================================================
+07/02/99
+
+ -- Added support for parameterized bind rules.
+ -- Added support for caching of ATTR rules using recompute.S
+
+ What's left for 5.0
+ -------------------
+ 1. Support for roles
+ 2. Re-architect user/group cache
+ 3. startup in multiple threads ( low priority)
+ 4. look at add/delete/modrdn operations.
+ 5. cleanup:
+ - revist all the debug statements
+ - new tests etc.
+ 6. UI work
+
+============
+commit:14/12/99 rbyrne
+
+. Added targattrfilters keyword for value based acls.
+ Required also slapi_filter_apply(), slapi_get_attribute_type()
+ and slapi_attr_syntax_normalize() in slapd (filter.c and attrsyntax.c).
+. Memory leak fix in acl.c for PListInit() call--see comments in code.
+. made access an int on it's own to give room for expansion
+ (see aci_access and aclpb_access)
+. files: ACL-Notes, acl.c acl.h acl-ext.c aclanom.c acllas.c acllist.c aclparse.c aclutil.c slapd/attrsyntax.c slapd/slapi-plugin.h slapd/filter.c slapd/libslapd.def
+
+===
+commit: Mon 20th Dec 199
+. aclparse.c: add proxy back to acl_access2str
+. filter.c: get_filter() does not recurse anymore--get_fitler_internal(), get_filter_list()
+do the recursion...this way testing for ldapsubentry works.
+. aclinit.c: now have filter (|(aci=*)(objectclass=ldapsubentry)) in
+aclinit_search_and_insert_aci(). This means that when slapi_search_internal_callback()
+stops returning subentries by default, we will still get them as we have the correct filter.
+
+===
+commit: 12/01/2000:
+. aclplugin.c: fix for proxyauth bug in aclplugin_preop_search() and
+acl_plugin_preop_modify()--the proxy_dn and dn were swapped.
+. acl_ext.c: Also, when we PListAssignValue() on DS_ATTR_USERDN in acl_init_aclpb(),
+we should pass it a dn from aclpb_sdn, NOT the dn passed into acl_init_aclpb() which
+gets freed after the call to acl_init_acpb(). JAlso here need to be careful thatif dn contains NULL that we indicate this in aclpb_sdn by setting dn to a non-NULL empty string ("") which the code takes to be anon.
+. checked that none of the PList objects (DS_PROP_ACLPB, DS_ATTR_USERDN, DS_ATTR_ENTRY) have mem leak problems.
+. acl.c, acllas.c, aclproxy.c: removed some #ifdef 0 and comments--tidy up but
+no code changes.
+. acl_ext.c: in acl__done_aclpb() we need to PListDleteProp() on ACL_ATTR_IP
+and ACL_ATTR_DNS. This is because if LASIpEval/ACL_GetAttribute() and
+LASDnsEval/ACL_GetAttribute() see that these properties exist, they do
+not bother calling the respective Getter() function. So, everytime
+the aclpb is reused and ip or dns eval is required, the old value is used (
+or whatever hjappens to be in the memory.). Tested--works fine now with ip and dns keywords. ALso tested that when the same user tries an a non-allowed machine he is not allowed by accident (as he was before).
+. in schema.c/oc_find(): normalize the objectclass name before looking for it. Otherwise
+if there's a trailing space in the oc name, you won't dfind it.
+
+===
+commit:
+
+. aclparse.c: fix for syntax.ksh tp6 test: if there is no "version" in an aci item, reject it.
+. acllas.c: in DS_UserDnEval() now call slapi_normalize_dn() when comparing param strings and
+ ordinary dns.
+. acl_ext.c: when seeting DS_USER_DN_ATTR, get the ndn, the normalized form.
+
+====
+commit: 7/02/2000
+anom profile and groupdn != don't work together! Bug 381830 in 4.X
+. acl.h: new bit in aci_type to mark as below.
+. aclparse.c: mark an aci if it's like deny() groupdn != blah
+. aclanom.c: if marked like that cancel anom profile (just like userdn !=)
+==
+. removed these for the mo...
+commit:
+. acllas.c: now get the vattrs via slapi_vattr_merge_copy() when testing the client entry.
+. vattr.c: assign i the length of the list:i = type_context.list_length;
+. entry.c: slapi_entry_add_valueset()
+
+==
+
+commit: 03/03/2000
+. support for roledn in acis.
+===
+. acllist: in slapi_sdn_free(&aciListHead->acic_sdn); gbeelato's mem leak fix.
+commited
+
+=====
+
+committed: 17/008/00
+. support for $dn: aclutil.c, aclparse.c, acllist.c, acllas.c, acl.c, acl.h
+. acl_ext.c:Make sure aclpb_search_base is initialized to NULL in aclpb__malloc()
+. acl.c: set_result_status: wrong bit masks were being used in a_eval->attrEval_s_astatus etc.
+ acl__attr_cached_result(): in the attr==NULL case, need to test for potential
+"recompute" case of attribute--this happens if it's a param or attr style aci.
+
+========
+commited
+Support for dynamic backends:
+. acllist.c, aclinit.c, libslapd.def, control.c, slapi-plugin.h:
+ acl_be_state_change_fnc(), slapi_build_control_from_berval() etc.
+. aclanom.c: logical error in aclanom_match_profile() was causing misctest4 to fail.
+. acl_ext.c:fix mem leak by calling acl_clean_aclEval_control() in acl_ext_conn_desctructor()
+.
+===
+committed:24 Aug 2000
+now SLAPI_ACL_ALL (allow(all)) does NOT include proxy right
+
+==
+committed: 30 Aug 2000
+. acl.c: new print_access_control_Summary() routine to display final acl status. Gets the proxy
+ stuff right too.
+ in acl__resource_match_aci() always test the TARGET_FILTER case, the old cod ethere was wrong.
+==
+. add support for macros to userdn ldapurl keyword.
+
+
+==
+Committed:
+. Sep 07 2000: Support for $attr in macros.
+. Sep 15 2000: Support for aci macros in targetfilter keyword.
+. Sep 18 2000: improve ret code handling in __aclinit_handler--stops spurious error message.
+
+
+--eof
diff --git a/ldap/servers/plugins/acl/Makefile b/ldap/servers/plugins/acl/Makefile
new file mode 100644
index 00000000..bdfe2dc0
--- /dev/null
+++ b/ldap/servers/plugins/acl/Makefile
@@ -0,0 +1,96 @@
+#
+# BEGIN COPYRIGHT BLOCK
+# Copyright 2001 Sun Microsystems, Inc.
+# Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+# All rights reserved.
+# END COPYRIGHT BLOCK
+#
+#
+# GNU Makefile for Directory Server acl-plugin.so acl plugins
+#
+
+LDAP_SRC = ../../..
+MCOM_ROOT = ../../../../..
+
+NOSTDCLEAN=true # don't let nsconfig.mk define target clean
+NOSTDSTRIP=true # don't let nsconfig.mk define target strip
+NSPR20=true # probably should be defined somewhere else (not sure where)
+
+OBJDEST = $(OBJDIR)/lib/libacl
+LIBDIR = $(LIB_RELDIR)
+
+include $(MCOM_ROOT)/ldapserver/nsconfig.mk
+include $(LDAP_SRC)/nsldap.mk
+
+# ACL plugin depends on libadminutil
+MCC_INCLUDE += $(ADMINUTIL_INCLUDE)
+
+ifeq ($(ARCH), WINNT)
+DEF_FILE:=./libacl.def
+endif
+
+CFLAGS+=$(SLCFLAGS)
+
+INCLUDES += -I$(LDAP_SRC)/servers/slapd -I$(ACLINC)
+
+ACL_OBJS= acl.o acllas.o aclutil.o aclplugin.o aclparse.o acl_ext.o aclproxy.o \
+ aclinit.o aclgroup.o aclanom.o acllist.o acleffectiverights.o
+
+OBJS = $(addprefix $(OBJDEST)/, $(ACL_OBJS))
+
+ifeq ($(ARCH), WINNT)
+LIBACL_DLL_OBJ = $(addprefix $(OBJDEST)/, acldllmain.o)
+endif
+
+LIBACL= $(addprefix $(LIBDIR)/, $(ACL_DLL).$(DLL_SUFFIX))
+
+ifeq ($(ARCH), WINNT)
+EXTRA_LIBS_DEP += $(LIBSLAPD_DEP)
+EXTRA_LIBS_DEP += $(LDAPSDK_DEP) $(NSPR_DEP)
+EXTRA_LIBS += $(LIBSLAPD) $(NSPRLINK) $(LDAP_LIBAVL) $(LDAP_SDK_LIBLDAP_DLL)
+endif
+
+# ACL plugin depends on libadminutil (through libns-httpd)
+EXTRA_LIBS_DEP += $(NSHTTPD_DEP) $(ADMINUTIL_DEP) $(DBM_DEP)
+EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(DBMLINK)
+
+ifeq ($(ARCH), WINNT)
+EXTRA_LIBS_DEP += $(LIBACCESS_DEP)
+EXTRA_LIBS += $(LIBACCESS)
+endif
+
+ifeq ($(ARCH), WINNT)
+DLL_LDFLAGS += -def:"./libacl.def"
+endif # WINNT
+
+ifeq ($(ARCH), AIX)
+EXTRA_LIBS_DEP += $(LIBSLAPD_DEP)
+EXTRA_LIBS_DEP += $(LDAPSDK_DEP) $(NSPR_DEP)
+EXTRA_LIBS += $(LIBSLAPDLINK) $(NSPRLINK) $(LDAP_LIBAVL) $(LDAP_SDK_LIBLDAP_DLL)
+EXTRA_LIBS += $(DLL_EXTRA_LIBS)
+LD=ld
+endif
+
+ifeq ($(ARCH), HPUX)
+EXTRA_LIBS_DEP += $(LDAPSDK_DEP) $(NSPR_DEP) $(SECURITY_DEP)
+EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(SECURITYLINK) $(NSPRLINK) $(ICULINK)
+endif
+
+clientSDK:
+
+all: $(OBJDEST) $(LIBDIR) $(LIBACL)
+
+$(LIBACL): $(OBJS) $(LIBACL_DLL_OBJ) $(DEF_FILE)
+ $(LINK_DLL) $(LIBACL_DLL_OBJ) $(PLATFORMLIBS) $(EXTRA_LIBS)
+
+veryclean: clean
+
+clean:
+ $(RM) $(OBJS)
+ifeq ($(ARCH), WINNT)
+ $(RM) $(LIBACL_DLL_OBJ)
+endif
+ $(RM) $(LIBACL)
+
+$(OBJDEST):
+ $(MKDIR) $(OBJDEST)
diff --git a/ldap/servers/plugins/acl/acl.c b/ldap/servers/plugins/acl/acl.c
new file mode 100644
index 00000000..8dfbd52a
--- /dev/null
+++ b/ldap/servers/plugins/acl/acl.c
@@ -0,0 +1,4118 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include "acl.h"
+
+/****************************************************************************
+*
+* acl.c
+*
+*
+* This file contains the functions related to Access Control List (ACL)
+* checking. The ACL checking is based on the ONE ACL design implemented
+* in the Web server 2.0. For more information on the ACL design look
+* into the barracuda home page.
+*
+*
+******************************************************************************/
+
+
+/****************************************************************************/
+/* Globals. Must be protected by Mutex. */
+/****************************************************************************/
+/* Signatures to see if things have changed */
+static short acl_signature = 0;
+
+/****************************************************************************/
+/* Defines, Constants, ande Declarations */
+/****************************************************************************/
+static char *ds_map_generic[2] = { NULL, NULL };
+
+/****************************************************************************/
+/* prototypes */
+/****************************************************************************/
+static int acl__resource_match_aci(struct acl_pblock *aclpb, aci_t *aci ,
+ int skip_attrEval, int *a_matched);
+static acl__TestRights(Acl_PBlock *aclpb,int access, char **right,
+ char ** map_generic, aclResultReason_t *result_reason);
+static int acl__scan_for_acis(struct acl_pblock *aclpb, int *err);
+static void acl__reset_cached_result (struct acl_pblock *aclpb );
+static int acl__scan_match_handles ( struct acl_pblock *aclpb, int type);
+static int acl__attr_cached_result (struct acl_pblock *aclpb, char *attr, int access );
+static int acl__match_handlesFromCache (struct acl_pblock *aclpb, char *attr, int access);
+static int acl__get_attrEval ( struct acl_pblock *aclpb, char *attr );
+static int acl__config_get_readonly ();
+static int acl__recompute_acl (Acl_PBlock *aclpb, AclAttrEval *a_eval,
+ int access, int aciIndex);
+static void __acl_set_aclIndex_inResult ( Acl_PBlock *aclpb,
+ int access, int index );
+static int acl__make_filter_test_entry ( Slapi_Entry **entry,
+ char *attr_type, struct berval *attr_val);
+static int acl__test_filter ( Slapi_Entry *entry, struct slapi_filter *f,
+ int filter_sense);
+static void print_access_control_summary( char * source,
+ int ret_val, char *clientDn,
+ struct acl_pblock *aclpb,
+ char *right,
+ char *attr,
+ const char *edn,
+ aclResultReason_t *acl_reason);
+static int check_rdn_access( Slapi_PBlock *pb,Slapi_Entry *e, char * newrdn,
+ int access);
+
+
+/*
+ * Check the rdn permissions for this entry:
+ * require: write access to the entry, write (add) access to the new
+ * naming attribute, write (del) access to the old naming attribute if
+ * deleteoldrdn set.
+ *
+ * Valid only for the modrdn operation.
+*/
+int
+acl_access_allowed_modrdn(
+ Slapi_PBlock *pb,
+ Slapi_Entry *e, /* The Slapi_Entry */
+ char *attr, /* Attribute of the entry */
+ struct berval *val, /* value of attr. NOT USED */
+ int access /* requested access rights */
+ )
+{
+ int retCode ;
+ char *newrdn, *oldrdn;
+ int deleteoldrdn = 0;
+
+ /*
+ * First check write permission on the entry--this is actually
+ * specially for modrdn.
+ */
+ retCode = acl_access_allowed ( pb, e, NULL /* attr */, NULL /* val */,
+ SLAPI_ACL_WRITE);
+
+ if ( retCode != LDAP_SUCCESS ) {
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "modrdn:write permission to entry not allowed\n");
+ return(retCode);
+ }
+
+ /* Now get the new rdn attribute name and value */
+
+ slapi_pblock_get( pb, SLAPI_MODRDN_TARGET, &oldrdn );
+ slapi_pblock_get( pb, SLAPI_MODRDN_NEWRDN, &newrdn );
+
+ /* Check can add the new naming attribute */
+ retCode = check_rdn_access( pb, e, newrdn, ACLPB_SLAPI_ACL_WRITE_ADD) ;
+ if ( retCode != LDAP_SUCCESS ) {
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "modrdn:write permission to add new naming attribute not allowed\n");
+ return(retCode);
+ }
+
+ /* Check can delete the new naming attribute--if required */
+ slapi_pblock_get( pb, SLAPI_MODRDN_DELOLDRDN, &deleteoldrdn );
+ if ( deleteoldrdn ) {
+ retCode = check_rdn_access( pb, e, oldrdn, ACLPB_SLAPI_ACL_WRITE_DEL) ;
+ if ( retCode != LDAP_SUCCESS ) {
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "modrdn:write permission to delete old naming attribute not allowed\n");
+ return(retCode);
+ }
+ }
+
+ return(retCode);
+
+}
+/*
+ * Test if have access to make the first rdn of dn in entry e.
+*/
+
+static int check_rdn_access( Slapi_PBlock *pb, Slapi_Entry *e, char *dn,
+ int access) {
+
+ char **dns;
+ char **rdns;
+ int retCode = LDAP_INSUFFICIENT_ACCESS;
+ int i;
+
+ if ( (dns = ldap_explode_dn( dn, 0 )) != NULL ) {
+
+ if ( (rdns = ldap_explode_rdn( dns[0], 0 )) != NULL ) {
+
+ for ( i = 0; rdns[i] != NULL; i++ ) {
+ char *type;
+ struct berval bv;
+
+ if ( slapi_rdn2typeval( rdns[i], &type, &bv ) != 0 ) {
+ char ebuf[ BUFSIZ ];
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "modrdn: rdn2typeval (%s) failed\n",
+ escape_string( rdns[i], ebuf ));
+ retCode = LDAP_INSUFFICIENT_ACCESS;
+ break;
+ } else {
+ if ( (retCode = acl_access_allowed ( pb, e, type /* attr */,
+ &bv /* val */,
+ access)) != LDAP_SUCCESS) {
+ break;
+ }
+ }
+ }
+ ldap_value_free( rdns );
+ }
+ ldap_value_free( dns );
+ }
+
+ return(retCode);
+}
+
+/***************************************************************************
+*
+* acl_access_allowed
+* Determines if access to the resource is allowed or not.
+*
+* Input:
+* 
+*
+* Returns:
+*
+* Returns success/Denied/error condition
+*
+* LDAP_SUCCESS -- access allowed
+* LDAP_INSUFFICIENT_ACCESS -- access denied
+*
+* Errors returned:
+*
+* Some of the definition of the return values used copied from
+* "ldap.h" for convienience.
+* LDAP_OPERATIONS_ERROR
+* LDAP_PROTOCOL_ERROR
+* LDAP_UNWILLING_TO_PERFORM
+*
+*
+* Error Handling:
+* Returned error code.
+**************************************************************************/
+int
+acl_access_allowed(
+ Slapi_PBlock *pb,
+ Slapi_Entry *e, /* The Slapi_Entry */
+ char *attr, /* Attribute of the entry */
+ struct berval *val, /* value of attr. NOT USED */
+ int access /* requested access rights */
+ )
+{
+ char *n_edn; /* Normalized DN of the entry */
+ int rv;
+ int err;
+ int ret_val;
+ char *right;
+ int num_handle;
+ struct acl_pblock *aclpb = NULL;
+ AclAttrEval *c_attrEval = NULL;
+ int got_reader_locked = 0;
+ int deallocate_attrEval = 0;
+ char ebuf [ BUFSIZ ];
+ char *clientDn;
+ Slapi_DN *e_sdn;
+ Slapi_Operation *op = NULL;
+ aclResultReason_t decision_reason;
+ int loglevel;
+
+ loglevel = slapi_is_loglevel_set(SLAPI_LOG_ACL) ? SLAPI_LOG_ACL : SLAPI_LOG_ACLSUMMARY;
+ slapi_pblock_get(pb, SLAPI_OPERATION, &op); /* for logging */
+
+ TNF_PROBE_1_DEBUG(acl_access_allowed_start,"ACL","",
+ tnf_int,access,access);
+
+ decision_reason.deciding_aci = NULL;
+ decision_reason.reason = ACL_REASON_NONE;
+
+ /**
+ * First, if the acl private write/delete on attribute right
+ * is requested, turn this into SLAPI_ACL_WRITE
+ * and record the original value.
+ * Need to make sure that these rights do not clash with the SLAPI
+ * public rights. This should be easy as the requested rights
+ * in the aclpb are stored in the bottom byte of aclpb_res_type,
+ * so if we keep the ACL private bits here too we make sure
+ * not to clash.
+ *
+ */
+
+ if ( access & (ACLPB_SLAPI_ACL_WRITE_ADD | ACLPB_SLAPI_ACL_WRITE_DEL) ) {
+ access |= SLAPI_ACL_WRITE;
+ }
+
+ n_edn = slapi_entry_get_ndn ( e );
+ e_sdn = slapi_entry_get_sdn ( e );
+
+ /* Check if this is a write operation and the database is readonly */
+ /* No one, even the rootdn should be allowed to write to the database */
+ /* jcm: ReadOnly only applies to the public backends, the private ones */
+ /* (the DSEs) should still be writable for configuration. */
+ if ( access & ( SLAPI_ACL_WRITE | SLAPI_ACL_ADD | SLAPI_ACL_DELETE )) {
+ int be_readonly, privateBackend;
+ Slapi_Backend *be;
+
+ slapi_pblock_get ( pb, SLAPI_BE_READONLY, &be_readonly );
+ slapi_pblock_get ( pb, SLAPI_BACKEND, &be );
+ privateBackend = slapi_be_private ( be );
+
+ if ( !privateBackend && (be_readonly || slapi_config_get_readonly () )){
+ slapi_log_error (loglevel, plugin_name,
+ "conn=%d op=%d (main): Deny %s on entry(%s)"
+ ": readonly backend\n",
+ op->o_connid, op->o_opid,
+ acl_access2str(access),
+ escape_string_with_punctuation(n_edn,ebuf));
+ return LDAP_UNWILLING_TO_PERFORM;
+ }
+ }
+
+ /* Check for things we need to skip */
+ TNF_PROBE_0_DEBUG(acl_skipaccess_start,"ACL","");
+ if ( acl_skip_access_check ( pb, e )) {
+ slapi_log_error (loglevel, plugin_name,
+ "conn=%d op=%d (main): Allow %s on entry(%s)"
+ ": root user\n",
+ op->o_connid, op->o_opid,
+ acl_access2str(access),
+ escape_string_with_punctuation(n_edn,ebuf));
+ return(LDAP_SUCCESS);
+ }
+ TNF_PROBE_0_DEBUG(acl_skipaccess_end,"ACL","");
+
+
+ /* Get the bindDN */
+ slapi_pblock_get ( pb, SLAPI_REQUESTOR_DN, &clientDn );
+
+ /* get the right acl pblock to work with */
+ if ( access & SLAPI_ACL_PROXY )
+ aclpb = acl_get_aclpb ( pb, ACLPB_PROXYDN_PBLOCK );
+ else
+ aclpb = acl_get_aclpb ( pb, ACLPB_BINDDN_PBLOCK );
+
+ if ( !aclpb ) {
+ slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, "Missing aclpb 1 \n" );
+ ret_val = LDAP_OPERATIONS_ERROR;
+ goto cleanup_and_ret;
+ }
+
+ /* check if aclpb is initialized or not */
+ TNF_PROBE_0_DEBUG(acl_aclpbinit_start,"ACL","");
+ acl_init_aclpb ( pb, aclpb, clientDn, 0 );
+ TNF_PROBE_0_DEBUG(acl_aclpbinit_end,"ACL","");
+
+
+ /* Here we mean if "I am trying to add/delete "myself" ? " */
+ if (val && (access & SLAPI_ACL_WRITE) && (val->bv_len > 0) ) {
+ /* should use slapi_sdn_compare() but that'a an extra malloc/free */
+
+ char *dn_val_to_write =
+ slapi_dn_normalize(slapi_ch_strdup(val->bv_val));
+
+ if ( aclpb->aclpb_authorization_sdn &&
+ slapi_utf8casecmp((ACLUCHP)dn_val_to_write, (ACLUCHP)
+ slapi_sdn_get_ndn(aclpb->aclpb_authorization_sdn)) == 0) {
+ access |= SLAPI_ACL_SELF;
+ }
+
+ slapi_ch_free( (void **)&dn_val_to_write);
+ }
+
+ /* Convert access to string of rights eg SLAPI_ACL_ADD->"add". */
+ if ((right= acl_access2str(access)) == NULL) {
+ /* ERROR: unknown rights */
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+ "acl_access_allowed unknown rights:%d\n", access);
+
+ ret_val = LDAP_OPERATIONS_ERROR;
+ goto cleanup_and_ret;
+ }
+
+
+ /*
+ * Am I a anonymous dude ? then we can use our anonymous profile
+ * We don't require the aclpb to have been initialized for anom stuff
+ *
+ */
+ TNF_PROBE_0_DEBUG(acl_anon_test_start,"ACL","");
+ if ( (access & (SLAPI_ACL_SEARCH | SLAPI_ACL_READ )) &&
+ (clientDn && *clientDn == '\0')) {
+ aclanom_get_suffix_info(e, aclpb);
+ ret_val = aclanom_match_profile ( pb, aclpb, e, attr, access );
+ if (ret_val != -1 ) {
+ if (ret_val == LDAP_SUCCESS ) {
+ decision_reason.reason = ACL_REASON_ANON_ALLOWED;
+ } else if (ret_val == LDAP_INSUFFICIENT_ACCESS) {
+ decision_reason.reason = ACL_REASON_ANON_DENIED;
+ }
+ goto cleanup_and_ret;
+ }
+ }
+ TNF_PROBE_0_DEBUG(acl_anon_test_end,"ACL","");
+
+ /* copy the value into the aclpb for later checking by the value acl code */
+
+ aclpb->aclpb_curr_attrVal = val;
+
+ if (!(aclpb->aclpb_state & ACLPB_SEARCH_BASED_ON_LIST) &&
+ (access & SLAPI_ACL_SEARCH)) {
+ /* We are evaluating SEARCH right for the entry. After that
+ ** we will eval the READ right. We need to refresh the
+ ** list of acls selected for evaluation for the entry.
+ ** Initialize the array so that we indicate nothing has been
+ ** selected.
+ */
+ aclpb->aclpb_handles_index[0] = -1;
+ /* access is not allowed on entry for search -- it's for
+ ** read only.
+ */
+ aclpb->aclpb_state &= ~ACLPB_ACCESS_ALLOWED_ON_ENTRY;
+ }
+
+ /* set that this is a new entry */
+ aclpb->aclpb_res_type |= ACLPB_NEW_ENTRY;
+ aclpb->aclpb_access = 0;
+ aclpb->aclpb_access |= access;
+
+ /*
+ * stub the Slapi_Entry info first time and only it has changed
+ * or if the pblock is a psearch pblock--in this case the lifetime
+ * of entries associated with psearches is such that we cannot cache
+ * pointers to them--we must always start afresh (see psearch.c).
+ */
+ slapi_pblock_get( pb, SLAPI_OPERATION, &op);
+ if ( operation_is_flag_set(op, OP_FLAG_PS) ||
+ (aclpb->aclpb_curr_entry_sdn == NULL) ||
+ (slapi_sdn_compare ( aclpb->aclpb_curr_entry_sdn, e_sdn) != 0)) {
+
+ TNF_PROBE_0_DEBUG(acl_entry_first_touch_start,"ACL","");
+
+ slapi_log_error(loglevel, plugin_name,
+ "#### conn=%d op=%d binddn=\"%s\"\n",
+ op->o_connid, op->o_opid, clientDn);
+ aclpb->aclpb_stat_total_entries++;
+
+ if (!(access & SLAPI_ACL_PROXY) &&
+ !( aclpb->aclpb_state & ACLPB_DONOT_EVALUATE_PROXY )) {
+ Acl_PBlock *proxy_pb;
+
+ proxy_pb = acl_get_aclpb( pb, ACLPB_PROXYDN_PBLOCK );
+ if (proxy_pb) {
+ TNF_PROBE_0_DEBUG(acl_access_allowed_proxy_start,"ACL","");
+ ret_val = acl_access_allowed( pb, e, attr, val, SLAPI_ACL_PROXY );
+ TNF_PROBE_0_DEBUG(acl_access_allowed_proxy_end,"ACL","");
+
+ if (ret_val != LDAP_SUCCESS) goto cleanup_and_ret;
+ }
+ }
+ if ( access & SLAPI_ACL_SEARCH) {
+ aclpb->aclpb_num_entries++;
+
+ if ( aclpb->aclpb_num_entries == 1) {
+ aclpb->aclpb_state |= ACLPB_COPY_EVALCONTEXT;
+ } else if ( aclpb->aclpb_state & ACLPB_COPY_EVALCONTEXT ) {
+ /* We need to copy the evalContext */
+ acl_copyEval_context ( aclpb, &aclpb->aclpb_curr_entryEval_context,
+ &aclpb->aclpb_prev_entryEval_context, 0 );
+ aclpb->aclpb_state &= ~ACLPB_COPY_EVALCONTEXT;
+ }
+ acl_clean_aclEval_context ( &aclpb->aclpb_curr_entryEval_context, 1 /*scrub */);
+ }
+
+ /* reset the cached result based on the scope */
+ acl__reset_cached_result (aclpb );
+
+ /* Find all the candidate aci's that apply by scanning up the DIT tree from edn. */
+
+ TNF_PROBE_0_DEBUG(acl_aciscan_start,"ACL","");
+ slapi_sdn_done ( aclpb->aclpb_curr_entry_sdn );
+ slapi_sdn_set_dn_byval ( aclpb->aclpb_curr_entry_sdn, n_edn );
+ acllist_aciscan_update_scan ( aclpb, n_edn );
+ TNF_PROBE_0_DEBUG(acl_aciscan_end,"ACL","");
+
+ /* Keep the ptr to the current entry */
+ aclpb->aclpb_curr_entry = (Slapi_Entry *) e;
+
+ /* Get the attr info */
+ deallocate_attrEval = acl__get_attrEval ( aclpb, attr );
+
+ aclutil_print_resource ( aclpb, right, attr, clientDn );
+
+ /*
+ * Used to be PListInitProp(aclpb->aclpb_proplist, 0,
+ * DS_ATTR_ENTRY, e, 0);
+ *
+ * The difference is that PListInitProp() allocates a new property
+ * every time it's called, overwriting the old name in the PList hash
+ * table, but not freeing the original property.
+ * Now, we just create the property at aclpb_malloc() time and
+ * Assign a new value each time.
+ */
+
+ rv = PListAssignValue(aclpb->aclpb_proplist,
+ DS_ATTR_ENTRY, e, 0);
+
+ if (rv < 0) {
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+ "Unable to set the Slapi_Entry in the Plist\n",0,0,0);
+ ret_val = LDAP_OPERATIONS_ERROR;
+ goto cleanup_and_ret;
+ }
+
+ TNF_PROBE_0_DEBUG(acl_entry_first_touch_end,"ACL","");
+
+ } else {
+ /* we are processing the same entry but for a different
+ ** attribute. If access is already allowed on that, entry, then
+ ** it's not a new entry anymore. It's the same old one.
+ */
+
+ TNF_PROBE_0_DEBUG(acl_entry_subs_touch_start,"ACL","");
+
+ aclpb->aclpb_res_type &= ~ACLPB_NEW_ENTRY;
+
+ /* Get the attr info */
+ deallocate_attrEval = acl__get_attrEval ( aclpb, attr );
+
+ TNF_PROBE_0_DEBUG(acl_entry_subs_touch_end,"ACL","");
+
+ }
+
+ /* get a lock for the reader */
+ acllist_acicache_READ_LOCK();
+ got_reader_locked = 1;
+
+ /*
+ ** Check if we can use any cached information to determine
+ ** access to this resource
+ */
+ if ( (access & SLAPI_ACL_SEARCH) &&
+ (ret_val = acl__match_handlesFromCache ( aclpb , attr, access)) != -1) {
+ /* means got a result: allowed or not*/
+
+ if (ret_val == LDAP_SUCCESS ) {
+ decision_reason.reason = ACL_REASON_EVALCONTEXT_CACHED_ALLOW;
+ } else if (ret_val == LDAP_INSUFFICIENT_ACCESS) {
+ decision_reason.reason =
+ ACL_REASON_EVALCONTEXT_CACHED_NOT_ALLOWED;
+ }
+ goto cleanup_and_ret;
+ }
+
+ /*
+ ** Now we have all the information about the resource. Now we need to
+ ** figure out if there are any ACLs which can be applied.
+ ** If no ACLs are there, then it's a DENY as default.
+ */
+ if (!(num_handle = acl__scan_for_acis(aclpb, &err))) {
+
+ /* We might have accessed the ACL first time which could
+ ** have caused syntax error.
+ */
+ if ( err == ACL_ONEACL_TEXT_ERR)
+ ret_val = LDAP_INVALID_SYNTAX;
+ else {
+ ret_val = LDAP_INSUFFICIENT_ACCESS;
+ decision_reason.reason = ACL_REASON_NO_MATCHED_RESOURCE_ALLOWS;
+ }
+ goto cleanup_and_ret;
+ }
+
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "Processed attr:%s for entry:%s\n", attr ? attr : "NULL",
+ ACL_ESCAPE_STRING_WITH_PUNCTUATION ( n_edn, ebuf), 0);
+
+ /*
+ ** Now evaluate the rights.
+ ** This is what we have been waiting for.
+ ** The return value should be ACL_RES_DENY or ACL_RES_ALLOW.
+ */
+ rv = acl__TestRights(aclpb, access, &right, ds_map_generic,
+ &decision_reason);
+ if ( rv != ACL_RES_ALLOW && (0 == strcasecmp ( right, "selfwrite") ) ) {
+ /* If I am adding myself to a group, we don't need selfwrite always,
+ ** write priv is good enough. Since libaccess doesn't provide me a nice
+ ** way to evaluate OR rights, I have to try again with wite priv.
+ ** bug: 339051
+ */
+ right = access_str_write;
+ rv = acl__TestRights(aclpb, access, &right, ds_map_generic,
+ &decision_reason);
+ }
+
+ if (rv == ACL_RES_ALLOW) {
+ ret_val = LDAP_SUCCESS;
+ } else {
+ ret_val = LDAP_INSUFFICIENT_ACCESS;
+ }
+
+cleanup_and_ret:
+
+ TNF_PROBE_0_DEBUG(acl_cleanup_start,"ACL","");
+
+ /* I am ready to get out. */
+ if ( got_reader_locked ) acllist_acicache_READ_UNLOCK();
+
+ /* Store the status of the evaluation for this attr */
+ if ( aclpb && (c_attrEval = aclpb->aclpb_curr_attrEval )) {
+ if ( deallocate_attrEval ) {
+ /* In this case we are not caching the result as
+ ** we have too many attrs. we have malloced space.
+ ** Get rid of it.
+ */
+ slapi_ch_free ( (void **) &c_attrEval->attrEval_name );
+ slapi_ch_free ( (void **) &c_attrEval );
+ } else if (ret_val == LDAP_SUCCESS ) {
+ if ( access & SLAPI_ACL_SEARCH )
+ c_attrEval->attrEval_s_status |= ACL_ATTREVAL_SUCCESS;
+ else if ( access & SLAPI_ACL_READ )
+ c_attrEval->attrEval_r_status |= ACL_ATTREVAL_SUCCESS;
+ else
+ c_attrEval->attrEval_r_status |= ACL_ATTREVAL_INVALID;
+ } else {
+ if ( access & SLAPI_ACL_SEARCH )
+ c_attrEval->attrEval_s_status |= ACL_ATTREVAL_FAIL;
+ else if ( access & SLAPI_ACL_READ )
+ c_attrEval->attrEval_r_status |= ACL_ATTREVAL_FAIL;
+ else
+ c_attrEval->attrEval_r_status |= ACL_ATTREVAL_INVALID;
+ }
+ }
+
+ if ( aclpb ) aclpb->aclpb_curr_attrEval = NULL;
+
+ print_access_control_summary( "main", ret_val, clientDn, aclpb, right,
+ (attr ? attr : "NULL"),
+ escape_string_with_punctuation (n_edn, ebuf),
+ &decision_reason);
+ TNF_PROBE_0_DEBUG(acl_cleanup_end,"ACL","");
+
+ TNF_PROBE_0_DEBUG(acl_access_allowed_end,"ACL","");
+
+ return(ret_val);
+
+}
+
+static void print_access_control_summary( char *source, int ret_val, char *clientDn,
+ struct acl_pblock *aclpb,
+ char *right,
+ char *attr,
+ const char *edn,
+ aclResultReason_t *acl_reason)
+{
+ struct codebook {
+ int code;
+ char *text;
+ };
+
+ static struct codebook reasonbook[] = {
+ {ACL_REASON_NO_ALLOWS, "no allow acis"},
+ {ACL_REASON_RESULT_CACHED_DENY, "cached deny"},
+ {ACL_REASON_RESULT_CACHED_ALLOW, "cached allow"},
+ {ACL_REASON_EVALUATED_ALLOW, "allowed"},
+ {ACL_REASON_EVALUATED_DENY, "denied"},
+ {ACL_REASON_NO_MATCHED_RESOURCE_ALLOWS, "no aci matched the resource"},
+ {ACL_REASON_NO_MATCHED_SUBJECT_ALLOWS, "no aci matched the subject"},
+ {ACL_REASON_ANON_ALLOWED, "allow anyone aci matched anon user"},
+ {ACL_REASON_ANON_DENIED, "no matching anyone aci for anon user"},
+ {ACL_REASON_EVALCONTEXT_CACHED_ALLOW, "cached context/parent allow"},
+ {ACL_REASON_EVALCONTEXT_CACHED_NOT_ALLOWED, "cached context/parent deny"},
+ {ACL_REASON_EVALCONTEXT_CACHED_ATTR_STAR_ALLOW, "cached context/parent allow any attr"},
+ {ACL_REASON_NONE, "error occurred"},
+ };
+
+ char *anon = "anonymous";
+ char *null_user = "NULL"; /* bizare case */
+ char *real_user = NULL;
+ char *proxy_user = NULL;
+ char *access_allowed_string = "Allow";
+ char *access_not_allowed_string = "Deny";
+ char *access_error_string = "access_error";
+ char *access_status = NULL;
+ char *access_reason_none = "no reason available";
+ char *access_reason = access_reason_none;
+ char acl_info[ BUFSIZ ];
+ Slapi_Operation *op = NULL;
+ int loglevel;
+ int i;
+
+ loglevel = slapi_is_loglevel_set(SLAPI_LOG_ACL) ? SLAPI_LOG_ACL : SLAPI_LOG_ACLSUMMARY;
+
+ if ( !slapi_is_loglevel_set(loglevel) ) {
+ return;
+ }
+
+ slapi_pblock_get(aclpb->aclpb_pblock, SLAPI_OPERATION, &op); /* for logging */
+
+ if (ret_val == LDAP_INSUFFICIENT_ACCESS) {
+ access_status = access_not_allowed_string;
+ } else if ( ret_val == LDAP_SUCCESS) {
+ access_status = access_allowed_string;
+ } else { /* some kind of error */
+ access_status = access_error_string;
+ }
+
+ /* decode the reason */
+ for (i = 0; i < sizeof(reasonbook) / sizeof(struct codebook); i++) {
+ if ( acl_reason->reason == reasonbook[i].code ) {
+ access_reason = reasonbook[i].text;
+ break;
+ }
+ }
+
+ /* get the acl */
+ acl_info[0] = '\0';
+ if (acl_reason->deciding_aci) {
+ if (acl_reason->reason == ACL_REASON_RESULT_CACHED_DENY ||
+ acl_reason->reason == ACL_REASON_RESULT_CACHED_ALLOW) {
+ /* acl is in cache. Its detail must have been printed before.
+ * So no need to print out acl detail this time.
+ */
+ PR_snprintf( &acl_info[0], BUFSIZ, "%s by aci(%d)",
+ access_reason,
+ acl_reason->deciding_aci->aci_index);
+ }
+ else {
+ PR_snprintf( &acl_info[0], BUFSIZ, "%s by aci(%d): aciname=%s, acidn=\"%s\"",
+ access_reason,
+ acl_reason->deciding_aci->aci_index,
+ acl_reason->deciding_aci->aclName,
+ slapi_sdn_get_ndn (acl_reason->deciding_aci->aci_sdn) );
+ }
+ }
+
+ /* Say who was denied access */
+
+ if (clientDn) {
+ if (clientDn[0] == '\0') {
+ /* anon */
+ real_user = anon;
+ } else {
+ real_user = clientDn;
+ }
+ } else {
+ real_user = null_user;
+ }
+
+ /* Is there a proxy */
+
+ if ( aclpb != NULL && aclpb->aclpb_proxy != NULL) {
+
+ if ( aclpb->aclpb_authorization_sdn != NULL ) {
+
+ proxy_user = (char *)(aclpb->aclpb_authorization_sdn->ndn ?
+ aclpb->aclpb_authorization_sdn->ndn:
+ null_user);
+
+ slapi_log_error(loglevel, plugin_name,
+ "conn=%d op=%d (%s): %s %s on entry(%s).attr(%s) to proxy (%s)"
+ ": %s\n",
+ op->o_connid, op->o_opid,
+ source,
+ access_status,
+ right,
+ edn,
+ attr ? attr: "NULL",
+ proxy_user,
+ acl_info[0] ? acl_info : access_reason);
+ } else {
+ proxy_user = null_user;
+ slapi_log_error(loglevel, plugin_name,
+ "conn=%d op=%d (%s): %s %s on entry(%s).attr(%s) to proxy (%s)"
+ ": %s\n",
+ op->o_connid, op->o_opid,
+ source,
+ access_status,
+ right,
+ edn,
+ attr ? attr: "NULL",
+ proxy_user,
+ acl_info[0] ? acl_info : access_reason);
+ }
+ } else{
+ slapi_log_error(loglevel, plugin_name,
+ "conn=%d op=%d (%s): %s %s on entry(%s).attr(%s)"
+ ": %s\n",
+ op->o_connid, op->o_opid,
+ source,
+ access_status,
+ right,
+ edn,
+ attr ? attr: "NULL",
+ acl_info[0] ? acl_info : access_reason);
+ }
+
+
+}
+/***************************************************************************
+*
+* acl_read_access_allowed_on_entry
+* check read access control on the given entry.
+*
+* Only used during seearch to test for read access on the entry.
+* (Could be generalized).
+*
+* attrs is the list of requested attributes passed with the search.
+* If the entry has no attributes (weird case) then the routine survives.
+*
+* Input:
+*
+*
+* Returns:
+* LDAP_SUCCESS - access allowed
+* LDAP_INSUFFICIENT_ACCESS - access denied
+*
+* Error Handling:
+* None.
+*
+**************************************************************************/
+int
+acl_read_access_allowed_on_entry (
+ Slapi_PBlock *pb,
+ Slapi_Entry *e, /* The Slapi_Entry */
+ char **attrs,
+ int access /* access rights */
+ )
+{
+
+ struct acl_pblock *aclpb;
+ Slapi_Attr *currAttr;
+ Slapi_Attr *nextAttr;
+ int len;
+ int attr_index = -1;
+ char *attr_type = NULL;
+ int rv, isRoot;
+ char *clientDn;
+ unsigned long flags;
+ aclResultReason_t decision_reason;
+ int loglevel;
+
+ loglevel = slapi_is_loglevel_set(SLAPI_LOG_ACL) ? SLAPI_LOG_ACL : SLAPI_LOG_ACLSUMMARY;
+
+ TNF_PROBE_0_DEBUG(acl_read_access_allowed_on_entry_start ,"ACL","");
+
+ decision_reason.deciding_aci = NULL;
+ decision_reason.reason = ACL_REASON_NONE;
+
+ slapi_pblock_get ( pb, SLAPI_REQUESTOR_ISROOT, &isRoot );
+
+ /*
+ ** If it's the root, or acl is off or the entry is a rootdse,
+ ** Then you have the privilege to read it.
+ */
+ if ( acl_skip_access_check ( pb, e ) ) {
+ char *n_edn = slapi_entry_get_ndn ( e );
+ char ebuf [ BUFSIZ ];
+ slapi_log_error (SLAPI_LOG_ACL, plugin_name,
+ "Root access (%s) allowed on entry(%s)\n",
+ acl_access2str(access),
+ ACL_ESCAPE_STRING_WITH_PUNCTUATION (n_edn, ebuf));
+ TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_entry_end ,"ACL","",
+ tnf_string,skip_access,"");
+ return LDAP_SUCCESS;
+ }
+
+ aclpb = acl_get_aclpb ( pb, ACLPB_BINDDN_PBLOCK );
+ if ( !aclpb ) {
+ slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, "Missing aclpb 2 \n" );
+ TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_entry_end ,"ACL","",
+ tnf_string,end,"aclpb error");
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+ /*
+ * Am I a anonymous dude ? then we can use our anonympous profile
+ * We don't require the aclpb to have been initialized for anom stuff
+ *
+ */
+ slapi_pblock_get ( pb, SLAPI_REQUESTOR_DN, &clientDn );
+ if ( clientDn && *clientDn == '\0' ) {
+ int ret_val;
+ ret_val = aclanom_match_profile ( pb, aclpb, e, NULL, SLAPI_ACL_READ );
+ TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_entry_end ,"ACL","",
+ tnf_string,end,"anon");
+
+ if (ret_val != -1 ) return ret_val;
+ }
+
+ aclpb->aclpb_state &= ~ACLPB_RESET_MASK;
+ if (aclpb->aclpb_state & ACLPB_MATCHES_ALL_ACLS ) {
+ int ret_val;
+ ret_val = acl__attr_cached_result (aclpb, NULL, SLAPI_ACL_READ);
+ if (ret_val != -1 ) {
+ /* print summary if loglevel set */
+ if ( slapi_is_loglevel_set(loglevel) ) {
+ char *n_edn;
+ n_edn = slapi_entry_get_ndn ( e );
+ if ( ret_val == LDAP_SUCCESS) {
+ decision_reason.reason =
+ ACL_REASON_EVALCONTEXT_CACHED_ALLOW;
+ } else {
+ decision_reason.reason =
+ ACL_REASON_EVALCONTEXT_CACHED_NOT_ALLOWED;
+ }
+ /*
+ * pass NULL as the attr as this routine is concerned with
+ * access at the entry level.
+ */
+ print_access_control_summary( "on entry",
+ ret_val, clientDn, aclpb,
+ acl_access2str(SLAPI_ACL_READ),
+ NULL, n_edn,
+ &decision_reason);
+ }
+ TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_entry_end ,"ACL","",
+ tnf_string,eval_context_cached,"");
+
+ return ret_val;
+ }
+ }
+
+ /*
+ * Currently do not use this code--it results in confusing
+ * behaviour..see 529905
+ */
+#ifdef DETERMINE_ACCESS_BASED_ON_REQUESTED_ATTRIBUTES
+
+ /* Do we have access to the entry by virtue of
+ ** having access to an attr. Before that, let's find out which attrs
+ ** the user want. If the user has specified certain attributes, then
+ ** we check aginst that set of attributes.
+ */
+ if (!((aclpb->aclpb_state & ACLPB_USER_WANTS_ALL_ATTRS) ||
+ (aclpb->aclpb_state & ACLPB_USER_SPECIFIED_ATTARS))) {
+ int i;
+ if (attrs == NULL) {
+ aclpb->aclpb_state |= ACLPB_USER_WANTS_ALL_ATTRS;
+ } else {
+ for ( i = 0; attrs != NULL && attrs[i] != NULL; i++ ) {
+ if ( strcmp( LDAP_ALL_USER_ATTRS, attrs[i] ) == 0 ) {
+ aclpb->aclpb_state |= ACLPB_USER_WANTS_ALL_ATTRS;
+ break;
+ }
+ }
+ }
+
+ if (!(aclpb->aclpb_state & ACLPB_USER_WANTS_ALL_ATTRS)) {
+ for (i = 0; attrs != NULL && attrs[i] != NULL; i++ ) {
+ if ( !slapi_entry_attr_find ( e, attrs[i], &currAttr ) ) {
+ aclpb->aclpb_state |= ACLPB_USER_SPECIFIED_ATTARS;
+ break;
+ }
+ }
+ }
+ } /* end of all user test*/
+
+
+ /*
+ ** If user has specified a list of attrs, might as well use it
+ ** to determine access control.
+ */
+ currAttr = NULL;
+ attr_index = -1;
+ if ( aclpb->aclpb_state & ACLPB_USER_SPECIFIED_ATTARS) {
+ attr_index = 0;
+ attr_type = attrs[attr_index++];
+ } else {
+ /* Skip the operational attributes -- if there are any in the front */
+ slapi_entry_first_attr ( e, &currAttr );
+ if (currAttr != NULL) {
+ slapi_attr_get_flags ( currAttr, &flags );
+ while ( flags & SLAPI_ATTR_FLAG_OPATTR ) {
+ flags = 0;
+ rv = slapi_entry_next_attr ( e, currAttr, &nextAttr );
+ if ( !rv ) slapi_attr_get_flags ( nextAttr, &flags );
+ currAttr = nextAttr;
+ }
+
+ /* Get the attr type */
+ if ( currAttr ) slapi_attr_get_type ( currAttr , &attr_type );
+ }
+ }
+
+#endif /*DETERMINE_ACCESS_BASED_ON_REQUESTED_ATTRIBUTES*/
+
+#ifndef DETERMINE_ACCESS_BASED_ON_REQUESTED_ATTRIBUTES
+
+ /*
+ * Here look at each attribute in the entry and see if
+ * we have read access to it--if we do
+ * and we are not denied access to the entry then this
+ * is taken as implying access to the entry.
+ */
+ slapi_entry_first_attr ( e, &currAttr );
+ if (currAttr != NULL) {
+ slapi_attr_get_type ( currAttr , &attr_type );
+ }
+#endif
+ aclpb->aclpb_state |= ACLPB_EVALUATING_FIRST_ATTR;
+
+ while (attr_type) {
+ if (acl_access_allowed (pb, e,attr_type, NULL,
+ SLAPI_ACL_READ) == LDAP_SUCCESS) {
+ /*
+ ** We found a rule which requires us to test access
+ ** to the entry.
+ */
+ if ( aclpb->aclpb_state & ACLPB_FOUND_A_ENTRY_TEST_RULE){
+ /* Do I have access on the entry itself */
+ if (acl_access_allowed (pb, e, NULL,
+ NULL, access) != LDAP_SUCCESS) {
+ /* How was I denied ?
+ ** I could be denied on a DENY rule or because
+ ** there is no allow rule. If it's a DENY from
+ ** a DENY rule, then we don't have access to
+ ** the entry ( nice trick to get in )
+ */
+ if ( aclpb->aclpb_state &
+ ACLPB_EXECUTING_DENY_HANDLES)
+ return LDAP_INSUFFICIENT_ACCESS;
+
+ /* The other case is I don't have an
+ ** explicit allow rule -- which is fine.
+ ** Since, I am already here, it means that I have
+ ** an implicit allow to the entry.
+ */
+ }
+ }
+ aclpb->aclpb_state &= ~ACLPB_EVALUATING_FIRST_ATTR;
+
+ /*
+ ** If we are not sending all the attrs, then we must
+ ** make sure that we have right on a attr that we are
+ ** sending
+ */
+ len = strlen(attr_type);
+ if ( len > ACLPB_MAX_ATTR_LEN) {
+ slapi_ch_free ( (void **) &aclpb->aclpb_Evalattr);
+ aclpb->aclpb_Evalattr = slapi_ch_malloc(len);
+ }
+ strncpy (aclpb->aclpb_Evalattr, attr_type, len);
+ aclpb->aclpb_Evalattr[len] = '\0';
+ if ( attr_index >= 0 ) {
+ /*
+ * access was granted to one of the user specified attributes
+ * which was found in the entry and that attribute is
+ * now in aclpb_Evalattr
+ */
+ aclpb->aclpb_state |=
+ ACLPB_ACCESS_ALLOWED_USERATTR;
+ } else {
+ /*
+ * Access was granted to _an_ attribute in the entry and that
+ * attribute is now in aclpb_Evalattr
+ */
+ aclpb->aclpb_state |=
+ ACLPB_ACCESS_ALLOWED_ON_A_ATTR;
+ }
+ TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_entry_end , "ACL","",
+ tnf_string,called_access_allowed,"");
+
+ return LDAP_SUCCESS;
+ } else {
+ /* try the next one */
+ attr_type = NULL;
+ if (attr_index >= 0) {
+ attr_type = attrs[attr_index++];
+ } else {
+ rv = slapi_entry_next_attr ( e, currAttr, &nextAttr );
+ if ( rv != 0 ) break;
+ currAttr = nextAttr;
+ slapi_attr_get_flags ( currAttr, &flags );
+ while ( flags & SLAPI_ATTR_FLAG_OPATTR ) {
+ flags = 0;
+ rv = slapi_entry_next_attr ( e, currAttr, &nextAttr );
+ if ( !rv ) slapi_attr_get_flags ( nextAttr, &flags );
+ currAttr = nextAttr;
+ }
+ /* Get the attr type */
+ if ( currAttr ) slapi_attr_get_type ( currAttr , &attr_type );
+ }
+ }
+ }
+
+ /*
+ ** That means. we have searched thru all the attrs and found
+ ** access is denied on all attrs.
+ **
+ ** If there were no attributes in the entry at all (can have
+ ** such entries thrown up by the b/e, then we do
+ ** not have such an implied access.
+ */
+ aclpb->aclpb_state |= ACLPB_ACCESS_DENIED_ON_ALL_ATTRS;
+ aclpb->aclpb_state &= ~ACLPB_EVALUATING_FIRST_ATTR;
+ TNF_PROBE_0_DEBUG(acl_read_access_allowed_on_entry_end ,"ACL","");
+
+ return LDAP_INSUFFICIENT_ACCESS;
+}
+
+/***************************************************************************
+*
+* acl_read_access_allowed_on_attr
+* check access control on the given attr.
+*
+* Only used during search to test for read access to an attr.
+* (Could be generalized)
+*
+* Input:
+*
+*
+* Returns:
+* LDAP_SUCCESS - access allowed
+* LDAP_INSUFFICIENT_ACCESS - access denied
+*
+* Error Handling:
+* None.
+*
+**************************************************************************/
+int
+acl_read_access_allowed_on_attr (
+ Slapi_PBlock *pb,
+ Slapi_Entry *e, /* The Slapi_Entry */
+ char *attr, /* Attribute of the entry */
+ struct berval *val, /* value of attr. NOT USED */
+ int access /* access rights */
+ )
+{
+
+ struct acl_pblock *aclpb = NULL;
+ char ebuf [ BUFSIZ ];
+ char *clientDn = NULL;
+ char *n_edn;
+ aclResultReason_t decision_reason;
+ int ret_val = -1;
+ int loglevel;
+
+ loglevel = slapi_is_loglevel_set(SLAPI_LOG_ACL) ? SLAPI_LOG_ACL : SLAPI_LOG_ACLSUMMARY;
+
+ TNF_PROBE_0_DEBUG(acl_read_access_allowed_on_attr_start ,"ACL","");
+
+ decision_reason.deciding_aci = NULL;
+ decision_reason.reason = ACL_REASON_NONE;
+
+ /* I am here, because I have access to the entry */
+
+ n_edn = slapi_entry_get_ndn ( e );
+
+ /* If it's the root or acl is off or rootdse, he has all the priv */
+ if ( acl_skip_access_check ( pb, e ) ) {
+ char ebuf [ BUFSIZ ];
+ slapi_log_error (SLAPI_LOG_ACL, plugin_name,
+ "Root access (%s) allowed on entry(%s)\n",
+ acl_access2str(access),
+ ACL_ESCAPE_STRING_WITH_PUNCTUATION (n_edn, ebuf), 0);
+ TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_attr_end ,"ACL","",
+ tnf_string,skip_aclcheck,"");
+
+ return LDAP_SUCCESS;
+ }
+
+ aclpb = acl_get_aclpb ( pb, ACLPB_BINDDN_PBLOCK );
+ if ( !aclpb ) {
+ slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, "Missing aclpb 3 \n" );
+ TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_attr_end ,"ACL","",
+ tnf_string,aclpb_error,"");
+
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+ /*
+ * Am I a anonymous dude ? then we can use our anonympous profile
+ * We don't require the aclpb to have been initialized for anom stuff
+ *
+ */
+ slapi_pblock_get (pb, SLAPI_REQUESTOR_DN ,&clientDn );
+ if ( clientDn && *clientDn == '\0' ) {
+ ret_val = aclanom_match_profile ( pb, aclpb, e, attr,
+ SLAPI_ACL_READ );
+ TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_attr_end ,"ACL","",
+ tnf_string,anon_decision,"");
+ if (ret_val != -1 ) return ret_val;
+ }
+
+ /* Then I must have a access to the entry. */
+ aclpb->aclpb_state |= ACLPB_ACCESS_ALLOWED_ON_ENTRY;
+
+ if ( aclpb->aclpb_state & ACLPB_MATCHES_ALL_ACLS ) {
+
+ ret_val = acl__attr_cached_result (aclpb, attr, SLAPI_ACL_READ);
+ if (ret_val != -1 ) {
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+ "MATCHED HANDLE:dn:%s attr: %s val:%d\n",
+ ACL_ESCAPE_STRING_WITH_PUNCTUATION (n_edn, ebuf), attr,
+ ret_val );
+ if ( ret_val == LDAP_SUCCESS) {
+ decision_reason.reason =
+ ACL_REASON_EVALCONTEXT_CACHED_ALLOW;
+ } else {
+ decision_reason.reason =
+ ACL_REASON_EVALCONTEXT_CACHED_NOT_ALLOWED;
+ }
+ goto acl_access_allowed_on_attr_Exit;
+ } else {
+ aclpb->aclpb_state |= ACLPB_COPY_EVALCONTEXT;
+ }
+ }
+
+ if (aclpb->aclpb_state & ACLPB_ACCESS_DENIED_ON_ALL_ATTRS) {
+ /* access is denied on all the attributes */
+ TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_attr_end ,"ACL","",
+ tnf_string,deny_all_attrs,"");
+
+ return LDAP_INSUFFICIENT_ACCESS;
+ }
+
+ /* do I have access to all the entries by virtue of having aci
+ ** rules with targetattr ="*". If yes, then allow access to
+ ** rest of the attributes.
+ */
+ if (aclpb->aclpb_state & ACLPB_ATTR_STAR_MATCHED) {
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+ "STAR Access allowed on attr:%s; entry:%s \n",
+ attr, ACL_ESCAPE_STRING_WITH_PUNCTUATION (n_edn, ebuf), 0);
+ decision_reason.reason =
+ ACL_REASON_EVALCONTEXT_CACHED_ATTR_STAR_ALLOW;
+ ret_val = LDAP_SUCCESS;
+ goto acl_access_allowed_on_attr_Exit;
+
+ }
+
+ if (aclpb->aclpb_state & ACLPB_ACCESS_ALLOWED_ON_A_ATTR) {
+
+ /* access is allowed on that attr.
+ ** for example: Slapi_Entry: cn, sn. phone, uid, passwd, address
+ ** We found that access is allowed on phone. That means the
+ ** -- access is denied on cn, sn
+ ** -- access is allowed on phone
+ ** -- Don't know about the rest. Need to evaluate.
+ */
+
+ if ( slapi_attr_type_cmp (attr, aclpb->aclpb_Evalattr, 1) == 0) {
+ /* from now on we need to evaluate access on
+ ** rest of the attrs.
+ */
+ aclpb->aclpb_state &= ~ACLPB_ACCESS_ALLOWED_ON_A_ATTR;
+ TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_attr_end ,"ACL","",
+ tnf_string,aclp_Evalattr1,"");
+
+ return LDAP_SUCCESS;
+ } else {
+ /*
+ * Here, the attr that implied access to the entry (aclpb_Evalattr),
+ * is not
+ * the one we currently want evaluated--so
+ * we need to evaluate access to attr--so fall through.
+ */
+ }
+
+ } else if (aclpb->aclpb_state & ACLPB_ACCESS_ALLOWED_USERATTR) {
+ /* Only skip evaluation on the user attr on which we have
+ ** evaluated before.
+ */
+ if ( slapi_attr_type_cmp (attr, aclpb->aclpb_Evalattr, 1) == 0) {
+ aclpb->aclpb_state &= ~ACLPB_ACCESS_ALLOWED_USERATTR;
+ TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_attr_end ,"ACL","",
+ tnf_string,aclp_Evalattr2,"");
+ return LDAP_SUCCESS;
+ }
+ }
+
+ /* we need to evaluate the access on this attr */
+ return ( acl_access_allowed(pb, e, attr, val, access) );
+
+ /* This exit point prints a summary and returns ret_val */
+acl_access_allowed_on_attr_Exit:
+
+ /* print summary if loglevel set */
+ if ( slapi_is_loglevel_set(loglevel) ) {
+
+ print_access_control_summary( "on attr",
+ ret_val, clientDn, aclpb,
+ acl_access2str(SLAPI_ACL_READ),
+ attr, n_edn, &decision_reason);
+ }
+ TNF_PROBE_0_DEBUG(acl_read_access_allowed_on_attr_end ,"ACL","");
+
+ return(ret_val);
+}
+/***************************************************************************
+*
+* acl_check_mods
+* check access control on the given entry to see if
+* it allows the given modifications by the user associated with op.
+*
+*
+* Input:
+*
+*
+* Returns:
+* LDAP_SUCCESS - mods allowed ok
+* <err> - same return value as acl_access_allowed()
+*
+* Error Handling:
+* None.
+*
+**************************************************************************/
+int
+acl_check_mods(
+ Slapi_PBlock *pb,
+ Slapi_Entry *e,
+ LDAPMod **mods,
+ char **errbuf
+)
+{
+ int i;
+ int rv, accessCheckDisabled;
+ int lastmod = 0;
+ Slapi_Attr *attr = NULL;
+ char *n_edn;
+ Slapi_Backend *be = NULL;
+ Slapi_DN *e_sdn;
+ Acl_PBlock *aclpb = acl_get_aclpb ( pb, ACLPB_PROXYDN_PBLOCK );
+ LDAPMod *mod;
+ Slapi_Mods smods;
+
+ rv = slapi_pblock_get ( pb, SLAPI_PLUGIN_DB_NO_ACL, &accessCheckDisabled );
+ if ( rv != -1 && accessCheckDisabled ) return LDAP_SUCCESS;
+
+ if ( NULL == aclpb )
+ aclpb = acl_get_aclpb ( pb, ACLPB_BINDDN_PBLOCK );
+
+ n_edn = slapi_entry_get_ndn ( e );
+ e_sdn = slapi_entry_get_sdn ( e );
+
+ slapi_mods_init_byref(&smods,mods);
+
+ for (mod = slapi_mods_get_first_mod(&smods);
+ mod != NULL;
+ mod = slapi_mods_get_next_mod(&smods)) {
+ switch (mod->mod_op & ~LDAP_MOD_BVALUES ) {
+
+ case LDAP_MOD_DELETE:
+ if (mod->mod_bvalues != NULL ) {
+ break;
+ }
+
+ /*
+ * Here, check that we have the right to delete all
+ * the values of the attribute in the entry.
+ */
+
+ case LDAP_MOD_REPLACE:
+ if ( !lastmod ) {
+ if (be == NULL) {
+ if (slapi_pblock_get( pb, SLAPI_BACKEND, &be )) {
+ be = NULL;
+ }
+ }
+ if (be != NULL)
+ slapi_pblock_get ( pb, SLAPI_BE_LASTMOD, &lastmod );
+ }
+ if (lastmod &&
+ (strcmp (mod->mod_type, "modifiersname")== 0 ||
+ strcmp (mod->mod_type, "modifytimestamp")== 0)) {
+ continue;
+ }
+
+ slapi_entry_attr_find (e, mod->mod_type, &attr);
+ if ( attr != NULL) {
+ Slapi_Value *sval=NULL;
+ const struct berval *attrVal=NULL;
+ int k= slapi_attr_first_value(attr,&sval);
+ while(k != -1) {
+ attrVal = slapi_value_get_berval(sval);
+ rv = slapi_access_allowed (pb, e,
+ mod->mod_type,
+ (struct berval *)attrVal, /* XXXggood had to cast away const - BAD */
+ ACLPB_SLAPI_ACL_WRITE_DEL); /* was SLAPI_ACL_WRITE */
+ if ( rv != LDAP_SUCCESS) {
+ acl_gen_err_msg (
+ SLAPI_ACL_WRITE,
+ n_edn,
+ mod->mod_type,
+ errbuf);
+ /* Cleanup */
+ slapi_mods_done(&smods);
+ return(rv);
+ }
+ k= slapi_attr_next_value(attr, k, &sval);
+ }
+ }
+ else {
+ rv = slapi_access_allowed (pb, e,
+ mod->mod_type,
+ NULL,
+ ACLPB_SLAPI_ACL_WRITE_DEL); /* was SLAPI_ACL_WRITE */
+ if ( rv != LDAP_SUCCESS) {
+ acl_gen_err_msg (
+ SLAPI_ACL_WRITE,
+ n_edn,
+ mod->mod_type,
+ errbuf);
+ /* Cleanup */
+ slapi_mods_done(&smods);
+ return(rv);
+ }
+ }
+ break;
+
+ default:
+ break;
+ } /* switch */
+
+ /*
+ * Check that we have add/delete writes on the specific values
+ * we are trying to add.
+ */
+
+ if ( aclpb && aclpb->aclpb_curr_entry_sdn )
+ slapi_sdn_done ( aclpb->aclpb_curr_entry_sdn );
+
+ if ( mod->mod_bvalues != NULL ) {
+
+ /*
+ * Here, there are specific values specified.
+ * For add and replace--we need add rights for these values.
+ * For delete we need delete rights for these values.
+ */
+
+ for ( i = 0; mod->mod_bvalues[i] != NULL; i++ ) {
+
+ if ( ((mod->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_ADD) ||
+ ((mod->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_REPLACE)) {
+
+ rv = acl_access_allowed (pb,e,
+ mod->mod_type,
+ mod->mod_bvalues[i],
+ ACLPB_SLAPI_ACL_WRITE_ADD); /*was SLAPI_ACL_WRITE*/
+ } else if ((mod->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_DELETE) {
+ rv = acl_access_allowed (pb,e,
+ mod->mod_type,
+ mod->mod_bvalues[i],
+ ACLPB_SLAPI_ACL_WRITE_DEL); /*was SLAPI_ACL_WRITE*/
+ } else {
+ rv = LDAP_INSUFFICIENT_ACCESS;
+ }
+
+ if ( rv != LDAP_SUCCESS ) {
+ acl_gen_err_msg (
+ SLAPI_ACL_WRITE,
+ n_edn,
+ mod->mod_type,
+ errbuf);
+ /* Cleanup */
+ slapi_mods_done(&smods);
+ return rv;
+ }
+ /* Need to check for all the values because
+ ** we may be modifying a "self<right>" value.
+ */
+
+ /* Are we adding/replacing a aci attribute
+ ** value. In that case, we need to make
+ ** sure that the new value has thr right
+ ** syntax
+ */
+ if (strcmp(mod->mod_type,
+ aci_attr_type) == 0) {
+ if ( 0 != (rv = acl_verify_syntax( e_sdn,
+ mod->mod_bvalues[i]))) {
+ aclutil_print_err(rv, e_sdn,
+ mod->mod_bvalues[i],
+ errbuf);
+ /* Cleanup */
+ slapi_mods_done(&smods);
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+ } /* for */
+ }
+ } /* end of big for */
+ /* Cleanup */
+ slapi_mods_done(&smods);
+ return( LDAP_SUCCESS );
+}
+/***************************************************************************
+*
+* acl_modified
+* Modifies ( removed, add, changes) the ACI LIST.
+*
+* Input:
+* int *optype - op code
+* char *dn - DN of the entry
+* void *change - The change struct which contais the
+* - change value
+*
+* Returns:
+* None.
+*
+* Error Handling:
+* None.
+*
+**************************************************************************/
+extern void
+acl_modified (Slapi_PBlock *pb, int optype, char *n_dn, void *change)
+{
+ struct berval **bvalue;
+ char **value;
+ int rv=0; /* returned value */
+ char* new_RDN;
+ char* parent_DN;
+ char* new_DN;
+ LDAPMod **mods;
+ struct berval b;
+ int j;
+ Slapi_Attr *attr = NULL;
+ Slapi_Entry *e = NULL;
+ char ebuf [ BUFSIZ];
+ Slapi_DN *e_sdn;
+ aclUserGroup *ugroup = NULL;
+
+ e_sdn = slapi_sdn_new_ndn_byval ( n_dn );
+ /* Before we proceed, Let's first check if we are changing any groups.
+ ** If we are, then we need to change the signature
+ */
+ switch ( optype ) {
+ case SLAPI_OPERATION_MODIFY:
+ case SLAPI_OPERATION_DELETE:
+ slapi_pblock_get(pb, SLAPI_ENTRY_PRE_OP, (void*)&e);
+ break;
+ case SLAPI_OPERATION_ADD:
+ e = (Slapi_Entry *)change;
+ break;
+ }
+
+ /* e can be null for RDN */
+ if ( e ) slapi_entry_attr_find( e, "objectclass", &attr);
+
+ if ( attr ) {
+ int group_change = 0;
+ Slapi_Value *sval=NULL;
+ const struct berval *attrVal;
+ int i;
+
+ i= slapi_attr_first_value ( attr,&sval );
+ while(i != -1) {
+ attrVal = slapi_value_get_berval ( sval );
+ if ( (strcasecmp (attrVal->bv_val, "groupOfNames") == 0 ) ||
+ (strcasecmp (attrVal->bv_val, "groupOfUniqueNames") == 0 ) ||
+ (strcasecmp (attrVal->bv_val, "groupOfCertificates") == 0 ) ||
+ (strcasecmp (attrVal->bv_val, "groupOfURLs") == 0 ) ) {
+ group_change= 1;
+ if ( optype == SLAPI_OPERATION_MODIFY ) {
+ Slapi_Attr *a = NULL;
+ int rv;
+ rv = slapi_entry_attr_find ( e, "uniqueMember", &a);
+ if ( rv != 0 ) break;
+ rv = slapi_entry_attr_find ( e, "Member", &a );
+ if ( rv != 0 ) break;
+ rv = slapi_entry_attr_find ( e, "MemberURL", &a );
+ if ( rv != 0 ) break;
+ /* That means we are not changing the member
+ ** list, so it's okay to let go this
+ ** change
+ */
+ group_change = 0;
+ }
+ break;
+ }
+ i= slapi_attr_next_value ( attr, i, &sval );
+ }
+
+ /*
+ ** We can do better here XXX, i.e invalidate the cache for users who
+ ** use this group. for now just do the whole thing.
+ */
+ if ( group_change ) {
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+ "Group Change: Invalidating entire UserGroup Cache\n",
+ ACL_ESCAPE_STRING_WITH_PUNCTUATION(n_dn, ebuf));
+ aclg_regen_group_signature();
+ if ( (optype == SLAPI_OPERATION_MODIFY) || (optype == SLAPI_OPERATION_DELETE ) ) {
+ /* Then we need to invalidate the acl signature also */
+ acl_signature = aclutil_gen_signature ( acl_signature );
+ }
+ }
+ }
+
+ /*
+ * Here if the target entry is in the group cache
+ * as a user then, as it's being changed it may move out of any dynamic
+ * groups it belongs to.
+ * Just remove it for now--can do better XXX by checking to see if
+ * it really needs to be removed by testing to see if he's
+ * still in th group after the change--but this requires keeping
+ * the memberURL of the group which we don't currently do.
+ * Also, if we keep
+ * the attributes that are being used in dynamic
+ * groups then we could only remove the user if a sensitive
+ * attribute was being modified (rather than scanning the whole user cache
+ * all the time). Also could do a hash lookup.
+ *
+ * aclg_find_userGroup() incs a refcnt so we can still refer to ugroup.
+ * aclg_markUgroupForRemoval() decs it and marks it for removal
+ * , so after that you cannot refer to ugroup.
+ *
+ */
+
+ if ( (ugroup = aclg_find_userGroup(n_dn)) != NULL) {
+ /*
+ * Mark this for deletion next time round--try to impact
+ * this mainline code as little as possible.
+ */
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+ "Marking entry %s for removal from ACL user Group Cache\n",
+ ACL_ESCAPE_STRING_WITH_PUNCTUATION(n_dn, ebuf));
+ aclg_markUgroupForRemoval (ugroup);
+ }
+
+ /*
+ * Take the write lock around all the mods--so that
+ * other operations will see the acicache either before the whole mod
+ * or after but not, as it was before, during the mod.
+ * This is in line with the LDAP concept of the operation
+ * on the whole entry being the atomic unit.
+ *
+ */
+
+ switch(optype) {
+ case SLAPI_OPERATION_DELETE:
+ /* In this case we have already checked if the user has
+ ** right to delete the entry. Part of delete of entry is
+ ** remove all the ACLs also.
+ */
+
+ acllist_acicache_WRITE_LOCK();
+ rv = acllist_remove_aci_needsLock(e_sdn, NULL);
+ acllist_acicache_WRITE_UNLOCK();
+
+ break;
+ case SLAPI_OPERATION_ADD:
+ slapi_entry_attr_find ( (Slapi_Entry *) change, aci_attr_type, &attr );
+
+ if ( attr ) {
+ Slapi_Value *sval=NULL;
+ const struct berval *attrVal;
+ int i;
+
+ acllist_acicache_WRITE_LOCK();
+ i= slapi_attr_first_value ( attr,&sval );
+ while ( i != -1 ) {
+ attrVal = slapi_value_get_berval(sval);
+ rv= acllist_insert_aci_needsLock(e_sdn, attrVal );
+ if (rv <= ACL_ERR)
+ aclutil_print_err(rv, e_sdn, attrVal, NULL);
+ /* Print the aci list */
+ i= slapi_attr_next_value ( attr, i, &sval );
+ }
+ acllist_acicache_WRITE_UNLOCK();
+ }
+ break;
+
+ case SLAPI_OPERATION_MODIFY:
+ {
+ int got_write_lock = 0;
+
+ mods = (LDAPMod **) change;
+
+ for (j=0; mods[j] != NULL; j++) {
+ if (strcasecmp(mods[j]->mod_type, aci_attr_type) == 0) {
+
+ /* Got an aci to mod in this list of mods, so
+ * take the acicache lock for the whole list of mods,
+ * remembering to free it below.
+ */
+ if ( !got_write_lock) {
+ acllist_acicache_WRITE_LOCK();
+ got_write_lock = 1;
+ }
+
+ switch (mods[j]->mod_op & ~LDAP_MOD_BVALUES) {
+ case LDAP_MOD_REPLACE:
+ /* First remove the item */
+ rv = acllist_remove_aci_needsLock(e_sdn, NULL);
+
+ /* now fall thru to add the new one */
+ case LDAP_MOD_ADD:
+ /* Add the new aci */
+ if (mods[j]->mod_op & LDAP_MOD_BVALUES) {
+ bvalue = mods[j]->mod_bvalues;
+ if (bvalue == NULL)
+ break;
+ for (; *bvalue != NULL; ++bvalue) {
+ rv=acllist_insert_aci_needsLock( e_sdn, *bvalue);
+ if (rv <= ACL_ERR) {
+ aclutil_print_err(rv, e_sdn,
+ *bvalue, NULL);
+ }
+ }
+ } else {
+ value = mods[j]->mod_values;
+ if (value == NULL)
+ break;
+ for (; *value != NULL; ++value) {
+ b.bv_len = strlen (*value);
+ b.bv_val = *value;
+ rv=acllist_insert_aci_needsLock( e_sdn, &b);
+ if (rv <= ACL_ERR) {
+ aclutil_print_err(rv, e_sdn,
+ &b, NULL);
+ }
+ }
+ }
+ break;
+ case LDAP_MOD_DELETE:
+ if (mods[j]->mod_op & LDAP_MOD_BVALUES) {
+ bvalue = mods[j]->mod_bvalues;
+ if (bvalue == NULL || *bvalue == NULL) {
+ rv = acllist_remove_aci_needsLock( e_sdn, NULL);
+ } else {
+ for (; *bvalue != NULL; ++bvalue)
+ acllist_remove_aci_needsLock( e_sdn, *bvalue);
+ }
+ } else {
+ value = mods[j]->mod_values;
+ if (value == NULL || *value == NULL) {
+ acllist_remove_aci_needsLock( e_sdn,NULL);
+ } else {
+ for (; *value != NULL; ++value) {
+ b.bv_len = strlen (*value);
+ b.bv_val = *value;
+ acllist_remove_aci_needsLock( e_sdn, &b);
+ }
+ }
+
+ }
+ break;
+
+ default:
+ break;
+ }/* modtype switch */
+ }/* attrtype is aci */
+ } /* end of for */
+ if ( got_write_lock ) {
+ acllist_acicache_WRITE_UNLOCK();
+ got_write_lock = 0;
+ }
+
+ break;
+ }/* case op is modify*/
+
+ case SLAPI_OPERATION_MODRDN:
+
+ new_RDN = (char*) change;
+ slapi_log_error (SLAPI_LOG_ACL, plugin_name,
+ "acl_modified (MODRDN %s => \"%s\"\n",
+ ACL_ESCAPE_STRING_WITH_PUNCTUATION (n_dn, ebuf), new_RDN, 0);
+
+ /* compute new_DN: */
+ parent_DN = slapi_dn_parent (n_dn);
+ if (parent_DN == NULL) {
+ new_DN = new_RDN;
+ } else {
+ new_DN = (char*) slapi_ch_malloc (strlen (new_RDN) + 3
+ + strlen (parent_DN));
+ strcpy (new_DN, new_RDN);
+ strcat (new_DN, ",");
+ strcat (new_DN, parent_DN);
+ slapi_dn_normalize (new_DN);
+ }
+
+ /* Change the acls */
+ acllist_acicache_WRITE_LOCK();
+ acllist_moddn_aci_needsLock ( e_sdn, new_DN );
+ acllist_acicache_WRITE_UNLOCK();
+
+ /* deallocat the parent_DN */
+ if (parent_DN != NULL) {
+ slapi_ch_free ( (void **) &new_DN );
+ slapi_ch_free ( (void **) &parent_DN );
+ }
+ break;
+
+ default:
+ /* print ERROR */
+ break;
+ } /*optype switch */
+
+ slapi_sdn_free ( &e_sdn );
+
+}
+/***************************************************************************
+*
+* acl__scan_for_acis
+* Scan the list and picup the correct acls for evaluation.
+*
+* Input:
+* Acl_PBlock *aclpb - Main ACL pblock
+* int *err; - Any error status
+* Returns:
+* num_handles - Number of handles matched to the
+* - resource + 1.
+* Error Handling:
+* None.
+*
+**************************************************************************/
+static int
+acl__scan_for_acis(Acl_PBlock *aclpb, int *err)
+{
+ aci_t *aci;
+ NSErr_t errp;
+ int attr_matched;
+ int deny_handle;
+ int allow_handle;
+ int gen_allow_handle = ACI_MAX_ELEVEL+1;
+ int gen_deny_handle = ACI_MAX_ELEVEL+1;
+ int i;
+ PRUint32 cookie;
+
+ TNF_PROBE_0_DEBUG(acl__scan_for_acis_start,"ACL","");
+
+ /*
+ ** Determine if we are traversing via the list Vs. we have our own
+ ** generated list
+ */
+ if ( aclpb->aclpb_state & ACLPB_SEARCH_BASED_ON_LIST ||
+ aclpb->aclpb_handles_index[0] != -1 ) {
+ int kk = 0;
+ while ( kk < ACLPB_MAX_SELECTED_ACLS && aclpb->aclpb_handles_index[kk] != -1 ) {
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name, "Using ACL Cointainer:%d for evaluation\n", kk);
+ kk++;
+ }
+ }
+
+ memset (&errp, 0, sizeof(NSErr_t));
+ *err = ACL_FALSE;
+ aclpb->aclpb_num_deny_handles = -1;
+ aclpb->aclpb_num_allow_handles = -1;
+ for (i=0; i <= ACI_MAX_ELEVEL; i++) {
+ aclpb->aclpb_deny_handles [i] = NULL;
+ aclpb->aclpb_allow_handles [i] = NULL;
+ }
+
+ /* Check the signature. If it has changed, start fresh */
+ if ( aclpb->aclpb_signature != acl_signature ) {
+ slapi_log_error (SLAPI_LOG_ACL, plugin_name,
+ "Restart the scan -- due to acl changes\n");
+ acllist_init_scan ( aclpb->aclpb_pblock, LDAP_SCOPE_BASE, NULL );
+ }
+
+ attr_matched = ACL_FALSE;
+ deny_handle = 0;
+ allow_handle = 0;
+ i = 0;
+
+ aclpb->aclpb_stat_acllist_scanned++;
+ aci = acllist_get_first_aci ( aclpb, &cookie );
+
+ while( aci ) {
+ if (acl__resource_match_aci(aclpb, aci, 0, &attr_matched)) {
+ /* Generate the ACL list handle */
+ if (aci->aci_handle == NULL) {
+ aci = acllist_get_next_aci ( aclpb, aci, &cookie );
+ continue;
+ }
+ aclutil_print_aci (aci, acl_access2str (aclpb->aclpb_access));
+
+ if (aci->aci_type & ACI_HAS_DENY_RULE) {
+ if (aclpb->aclpb_deny_handles[aci->aci_elevel] == NULL ) {
+ aclpb->aclpb_deny_handles[aci->aci_elevel] = aci;
+ } else {
+ if ((gen_deny_handle + ACI_DEFAULT_ELEVEL + 1) ==
+ aclpb->aclpb_deny_handles_size ) {
+ int num = ACLPB_INCR_LIST_HANDLES +
+ aclpb->aclpb_deny_handles_size;
+ /* allocate more space */
+ aclpb->aclpb_deny_handles =
+ (aci_t **)
+ slapi_ch_realloc (
+ (void *) aclpb->aclpb_deny_handles,
+ num * sizeof (aci_t *));
+ aclpb->aclpb_deny_handles_size = num;
+ }
+ aclpb->aclpb_deny_handles [gen_deny_handle] = aci;
+ gen_deny_handle++;
+ }
+ deny_handle++;
+ }
+ /*
+ ** It's possible that a single acl is in both the camps i.e
+ ** It has a allow and a deny rule
+ ** In that case we keep the same acl in both the camps.
+ */
+ if (aci->aci_type & ACI_HAS_ALLOW_RULE) {
+ if (aclpb->aclpb_allow_handles[aci->aci_elevel] == NULL ) {
+ aclpb->aclpb_allow_handles[aci->aci_elevel] = aci;
+ } else {
+ if ((gen_allow_handle + ACI_DEFAULT_ELEVEL + 1) ==
+ aclpb->aclpb_allow_handles_size) {
+ /* allocate more space */
+ int num = ACLPB_INCR_LIST_HANDLES +
+ aclpb->aclpb_allow_handles_size;
+
+ aclpb->aclpb_allow_handles =
+ (aci_t **)
+ slapi_ch_realloc (
+ (void *) aclpb->aclpb_allow_handles,
+ num * sizeof (aci_t *));
+ aclpb->aclpb_allow_handles_size = num;
+ }
+ aclpb->aclpb_allow_handles [gen_allow_handle] = aci;
+ gen_allow_handle++;
+ }
+ allow_handle++;
+ }
+ }
+ aci = acllist_get_next_aci ( aclpb, aci, &cookie );
+ } /* end of while */
+
+ /* make the last one a null */
+ aclpb->aclpb_deny_handles [gen_deny_handle] = NULL;
+ aclpb->aclpb_allow_handles [gen_allow_handle] = NULL;
+
+ /* specify how many we found */
+ aclpb->aclpb_num_deny_handles = deny_handle;
+ aclpb->aclpb_num_allow_handles = allow_handle;
+
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name, "Num of ALLOW Handles:%d, DENY handles:%d\n",
+ aclpb->aclpb_num_allow_handles, aclpb->aclpb_num_deny_handles, 0);
+
+ TNF_PROBE_0_DEBUG(acl__scan_for_acis_end,"ACL","");
+
+ return(allow_handle + deny_handle);
+}
+
+/***************************************************************************
+*
+* acl__resource_match_aci
+*
+* This compares the ACI for the given resource and determines if
+* the ACL applies to the resource or not.
+*
+* For read/search operation, we collect all the possible acls which
+* will apply, We will be using this list for future acl evaluation
+* for that entry.
+*
+* Input:
+* struct acl_pblock *aclpb - Main acl private block
+* aci_t *aci - The ACI item
+* int skip_attrEval - DOn't check for attrs
+* int *a_matched - Attribute matched
+*
+* Returns:
+*
+* ACL_TRUE - Yep, This ACL is applicable to the
+* - the resource.
+* ACL_FALSE - No it is not.
+*
+* Error Handling:
+* None.
+*
+**************************************************************************/
+#define ACL_RIGHTS_TARGETATTR_NOT_NEEDED ( SLAPI_ACL_ADD | SLAPI_ACL_DELETE | SLAPI_ACL_PROXY)
+static int
+acl__resource_match_aci( Acl_PBlock *aclpb, aci_t *aci, int skip_attrEval, int *a_matched)
+{
+
+ struct slapi_filter *f; /* filter */
+ int rv; /* return value */
+ int matches;
+ int attr_matched;
+ int attr_matched_in_targetattrfilters = 0;
+ int dn_matched;
+ char *res_attr;
+ int aci_right = 0;
+ int res_right = 0;
+ int star_matched = ACL_FALSE;
+ int num_attrs = 0;
+ AclAttrEval *c_attrEval = NULL;
+ const char *res_ndn = NULL;
+ const char *aci_ndn = NULL;
+ char *matched_val = NULL;
+ int add_matched_val_to_ht = 0;
+ char res_right_str[128];
+
+ TNF_PROBE_0_DEBUG(acl__resource_match_aci_start,"ACL","");
+
+ aclpb->aclpb_stat_aclres_matched++;
+
+ /* Assume that resource matches */
+ matches = ACL_TRUE;
+
+ /* Figure out if the acl has the correct rights or not */
+ aci_right = aci->aci_access;
+ res_right = aclpb->aclpb_access;
+ if (!(aci_right & res_right)) {
+ /* If we are looking for read/search and the acl has read/search
+ ** then go further because if targets match we may keep that
+ ** acl in the entry cache list.
+ */
+ if (!((res_right & (SLAPI_ACL_SEARCH | SLAPI_ACL_READ)) &&
+ (aci_right & (SLAPI_ACL_SEARCH | SLAPI_ACL_READ))))
+ matches = ACL_FALSE;
+ goto acl__resource_match_aci_EXIT;
+ }
+
+
+ /* first Let's see if the entry is under the subtree where the
+ ** ACL resides. We can't let somebody affect a target beyond the
+ ** scope of where the ACL resides
+ ** Example: ACL is located in "ou=engineering, o=ace industry, c=us
+ ** but if the target is "o=ace industry, c=us", then we are in trouble.
+ **
+ ** If the aci is in the rootdse and the entry is not, then we do not
+ ** match--ie. acis in the rootdse do NOT apply below...for the moment.
+ **
+ */
+ res_ndn = slapi_sdn_get_ndn ( aclpb->aclpb_curr_entry_sdn );
+ aci_ndn = slapi_sdn_get_ndn ( aci->aci_sdn );
+ if (!slapi_sdn_issuffix(aclpb->aclpb_curr_entry_sdn, aci->aci_sdn)
+ || (!slapi_is_rootdse(res_ndn) && slapi_is_rootdse(aci_ndn)) ) {
+
+ /* cant' poke around */
+ matches = ACL_FALSE;
+ goto acl__resource_match_aci_EXIT;
+ }
+
+ /*
+ ** We have a single ACI which we need to find if it applies to
+ ** the resource or not.
+ */
+ if ((aci->aci_type & ACI_TARGET_DN) &&
+ (aclpb->aclpb_curr_entry_sdn)) {
+ char *avaType;
+ struct berval *avaValue;
+
+ f = aci->target;
+ dn_matched = ACL_TRUE;
+ slapi_filter_get_ava ( f, &avaType, &avaValue );
+
+ if (!slapi_dn_issuffix( res_ndn, avaValue->bv_val)) {
+ dn_matched = ACL_FALSE;
+ }
+ if (aci->aci_type & ACI_TARGET_NOT) {
+ matches = (dn_matched ? ACL_FALSE : ACL_TRUE);
+ } else {
+ matches = (dn_matched ? ACL_TRUE: ACL_FALSE);
+ }
+ }
+
+ /* No need to look further */
+ if (matches == ACL_FALSE) {
+ goto acl__resource_match_aci_EXIT;
+ }
+
+ if (aci->aci_type & ACI_TARGET_PATTERN) {
+
+ f = aci->target;
+ dn_matched = ACL_TRUE;
+
+ if ((rv = acl_match_substring(f, (char *)res_ndn, 0 /* match suffux */)) != ACL_TRUE) {
+ dn_matched = ACL_FALSE;
+ if(rv == ACL_ERR) {
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+ "acl__resource_match_aci:pattern err\n",
+ 0,0,0);
+ matches = ACL_FALSE;
+ goto acl__resource_match_aci_EXIT;
+ }
+ }
+ if (aci->aci_type & ACI_TARGET_NOT) {
+ matches = (dn_matched ? ACL_FALSE : ACL_TRUE);
+ } else {
+ matches = (dn_matched ? ACL_TRUE: ACL_FALSE);
+ }
+ }
+
+ /* No need to look further */
+ if (matches == ACL_FALSE) {
+ goto acl__resource_match_aci_EXIT;
+ }
+
+ /*
+ * Is it a (target="ldap://cn=*,($dn),o=sun.com") kind of thing.
+ */
+ if (aci->aci_type & ACI_TARGET_MACRO_DN) {
+ /*
+ * See if the ($dn) component matches the string and
+ * retrieve the matched substring for later use
+ * in the userdn.
+ * The macro string is a function of the dn only, so if the
+ * entry is the same one don't recalculate it--
+ * this flag only works for search right now, could
+ * also optimise for mods by making it work for mods.
+ */
+
+ if ( (aclpb->aclpb_res_type & ACLPB_NEW_ENTRY) == 0 ) {
+ /*
+ * Here same entry so just look up the matched value,
+ * calculated from the targetdn and stored judiciously there
+ */
+ matched_val = (char *)acl_ht_lookup( aclpb->aclpb_macro_ht,
+ (PLHashNumber)aci->aci_index);
+ }
+ if ( matched_val == NULL &&
+ (aclpb->aclpb_res_type & (ACLPB_NEW_ENTRY | ACLPB_EFFECTIVE_RIGHTS))) {
+
+ slapi_log_error (SLAPI_LOG_ACL, plugin_name,
+ "Evaluating macro aci(%d)%s for resource %s\n",
+ aci->aci_index, aci->aclName,
+ aclutil__access_str(res_right, res_right_str));
+ matched_val = acl_match_macro_in_target( res_ndn,
+ aci->aci_macro->match_this,
+ aci->aci_macro->macro_ptr);
+ add_matched_val_to_ht = 1; /* may need to add matched value to ht */
+ }
+ if (matched_val == NULL) {
+ dn_matched = ACL_FALSE;
+ } else {
+ dn_matched = ACL_TRUE;
+ }
+
+ if (aci->aci_type & ACI_TARGET_NOT) {
+ matches = (dn_matched ? ACL_FALSE : ACL_TRUE);
+ } else {
+ matches = (dn_matched ? ACL_TRUE: ACL_FALSE);
+ }
+
+ if ( add_matched_val_to_ht ) {
+ if ( matches == ACL_TRUE && matched_val ) {
+ /*
+ * matched_val may be needed later for matching on
+ * other targets or on the subject--so optimistically
+ * put it in the hash table.
+ * If, at the end of this routine, we
+ * find that after all the resource did not match then
+ * that's ok--the value is freed at aclpb cleanup time.
+ * If there is already an entry for this aci in this
+ * aclpb then remove it--it's an old value for a
+ * different entry.
+ */
+
+ acl_ht_add_and_freeOld(aclpb->aclpb_macro_ht,
+ (PLHashNumber)aci->aci_index,
+ matched_val);
+ slapi_log_error (SLAPI_LOG_ACL, plugin_name,
+ "-- Added aci(%d) and matched value (%s) to macro ht\n",
+ aci->aci_index, matched_val);
+ acl_ht_display_ht(aclpb->aclpb_macro_ht);
+ } else {
+ slapi_ch_free((void **)&matched_val);
+ if (matches == ACL_FALSE) {
+ slapi_log_error (SLAPI_LOG_ACL, plugin_name,
+ "Evaluated ACL_FALSE\n");
+ }
+ }
+ }
+ } /* MACRO_DN */
+
+ /* No need to look further */
+ if (matches == ACL_FALSE) {
+ goto acl__resource_match_aci_EXIT;
+ }
+
+ /*
+ ** Here, if there's a targetfilter field, see if it matches.
+ **
+ ** The commented out code below was an erroneous attempt to skip
+ ** this test. It is wrong because: 1. you need to store
+ ** whether the last test matched or not (you cannot just assume it did)
+ ** and 2. It may not be the same aci, so the previous matched
+ ** value is a function of the aci.
+ ** May be interesting to build such a cache...but no evidence for
+ ** for that right now. See Bug 383424.
+ **
+ **
+ ** && ((aclpb->aclpb_state & ACLPB_SEARCH_BASED_ON_LIST) ||
+ ** (aclpb->aclpb_res_type & ACLPB_NEW_ENTRY))
+ */
+ if (aci->aci_type & ACI_TARGET_FILTER ) {
+ int filter_matched = ACL_TRUE;
+
+ /*
+ * Check for macros.
+ * For targetfilter we need to fake the lasinfo structure--it's
+ * created "naturally" for subjects but not targets.
+ */
+
+
+ if ( aci->aci_type & ACI_TARGET_FILTER_MACRO_DN) {
+
+ lasInfo *lasinfo = NULL;
+
+ lasinfo = (lasInfo*) slapi_ch_malloc( sizeof(lasInfo) );
+
+ lasinfo->aclpb = aclpb;
+ lasinfo->resourceEntry = aclpb->aclpb_curr_entry;
+ aclpb->aclpb_curr_aci = aci;
+ filter_matched = aclutil_evaluate_macro( aci->targetFilterStr,
+ lasinfo,
+ ACL_EVAL_TARGET_FILTER);
+ slapi_ch_free((void**)&lasinfo);
+ } else {
+
+
+ if (slapi_vattr_filter_test(NULL, aclpb->aclpb_curr_entry,
+ aci->targetFilter,
+ 0 /*don't do acess chk*/)!= 0) {
+ filter_matched = ACL_FALSE;
+ }
+
+ }
+
+ /* If it's a logical value we can do logic on it...otherwise we do not match */
+ if ( filter_matched == ACL_TRUE || filter_matched == ACL_FALSE) {
+ if (aci->aci_type & ACI_TARGET_FILTER_NOT) {
+ matches = (filter_matched == ACL_TRUE ? ACL_FALSE : ACL_TRUE);
+ } else {
+ matches = (filter_matched == ACL_TRUE ? ACL_TRUE: ACL_FALSE);
+ }
+ } else {
+ matches = ACL_FALSE;
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "Returning UNDEFINED for targetfilter evaluation.\n");
+ }
+
+ if (matches == ACL_FALSE) {
+ goto acl__resource_match_aci_EXIT;
+ }
+ }
+
+ /*
+ * Check to see if we need to evaluate any targetattrfilters.
+ * They look as follows:
+ * (targetattrfilters="add=sn:(sn=rob) && gn:(gn!=byrne),
+ * del=sn:(sn=rob) && gn:(gn=byrne)")
+ *
+ * For ADD/DELETE:
+ * If theres's a targetattrfilter then each add/del filter
+ * that applies to an attribute in the entry, must be satisfied
+ * by each value of the attribute in the entry.
+ *
+ * For MODIFY:
+ * If there's a targetattrfilter then the add/del filter
+ * must be satisfied by the attribute to be added/deleted.
+ * (MODIFY acl is evaluated one value at a time).
+ *
+ *
+ */
+
+ if ((aclpb->aclpb_access & SLAPI_ACL_ADD &&
+ aci->aci_type & ACI_TARGET_ATTR_ADD_FILTERS )||
+ (aclpb->aclpb_access & SLAPI_ACL_DELETE &&
+ aci->aci_type & ACI_TARGET_ATTR_DEL_FILTERS ) ) {
+
+ Targetattrfilter **attrFilterArray;
+
+ Targetattrfilter *attrFilter = NULL;
+
+ int found_applicable = 0;
+ Slapi_Attr *attr_ptr = NULL;
+ Slapi_Value *sval;
+ const struct berval *attrVal;
+ int k;
+ int done;
+
+
+ if (aclpb->aclpb_access & SLAPI_ACL_ADD &&
+ aci->aci_type & ACI_TARGET_ATTR_ADD_FILTERS) {
+
+ attrFilterArray = aci->targetAttrAddFilters;
+
+ } else if (aclpb->aclpb_access & SLAPI_ACL_DELETE &&
+ aci->aci_type & ACI_TARGET_ATTR_DEL_FILTERS) {
+
+ attrFilterArray = aci->targetAttrDelFilters;
+
+ }
+
+ attr_matched = ACL_TRUE;
+ num_attrs = 0;
+
+ while (attrFilterArray[num_attrs] && attr_matched) {
+ attrFilter = attrFilterArray[num_attrs];
+
+ /*
+ * If this filter applies to an attribute in the entry,
+ * apply it to the entry.
+ * Otherwise just ignore it.
+ *
+ */
+
+ if (slapi_entry_attr_find ( aclpb->aclpb_curr_entry,
+ attrFilter->attr_str,
+ &attr_ptr) == 0) {
+
+ /*
+ * This is an applicable filter.
+ * The filter is to be appplied to the entry being added
+ * or deleted.
+ * The filter needs to be satisfied by _each_ occurence
+ * of the attribute in the entry--otherwise you
+ * could satisfy the filter and then put loads of other
+ * values in on the back of it.
+ */
+
+ found_applicable = 1;
+
+ sval=NULL;
+ attrVal=NULL;
+ k= slapi_attr_first_value(attr_ptr,&sval);
+ done = 0;
+ while(k != -1 && !done) {
+ attrVal = slapi_value_get_berval(sval);
+
+ if ( acl__make_filter_test_entry(
+ &aclpb->aclpb_filter_test_entry,
+ attrFilter->attr_str,
+ (struct berval *)attrVal) == LDAP_SUCCESS ) {
+
+ attr_matched= acl__test_filter(
+ aclpb->aclpb_filter_test_entry,
+ attrFilter->filter,
+ 1 /* Do filter sense evaluation below */
+ );
+ done = !attr_matched;
+ slapi_entry_free( aclpb->aclpb_filter_test_entry );
+ }
+
+ k= slapi_attr_next_value(attr_ptr, k, &sval);
+ }/* while */
+
+ /*
+ * Here, we applied an applicable filter to the entry.
+ * So if attr_matched is ACL_TRUE then every value
+ * of the attribute in the entry satisfied the filter.
+ * Otherwise, attr_matched is ACL_FALSE and not every
+ * value satisfied the filter, so we will teminate the
+ * scan of the filter list.
+ */
+
+ }
+
+ num_attrs++;
+ } /* while */
+
+ /*
+ * Here, we've applied all the applicable filters to the entry.
+ * Each one must have been satisfied by all the values of the attribute.
+ * The result of this is stored in attr_matched.
+ */
+
+#if 0
+ /*
+ * Don't support a notion of "add != " or "del != "
+ * at the moment.
+ * To do this, need to test whether it's an add test or del test
+ * then if it's add and ACI_TARGET_ATTR_ADD_FILTERS_NOT then
+ * flip the bit. Same for del.
+ */
+
+ if (aci->aci_type & ACI_TARGET_ATTR_DEL_FILTERS_NOT) {
+ matches = (matches ? ACL_FALSE : ACL_TRUE);
+ } else {
+ matches = (matches ? ACL_TRUE: ACL_FALSE);
+ }
+#endif
+
+ /* No need to look further */
+ if (attr_matched == ACL_FALSE) {
+ matches = ACL_FALSE;
+ goto acl__resource_match_aci_EXIT;
+ }
+
+ } else if ( (aclpb->aclpb_access & ACLPB_SLAPI_ACL_WRITE_ADD &&
+ aci->aci_type & ACI_TARGET_ATTR_ADD_FILTERS) ||
+ (aclpb->aclpb_access & ACLPB_SLAPI_ACL_WRITE_DEL &&
+ aci->aci_type & ACI_TARGET_ATTR_DEL_FILTERS ) ) {
+
+
+ /*
+ * Here, it's a modify add/del and we have attr filters.
+ * So, we need to scan the add/del filter list to find the filter
+ * that applies to the current attribute.
+ * Then the (attribute,value) pair being added/deleted better
+ * match that filter.
+ *
+ *
+ */
+
+ Targetattrfilter **attrFilterArray = NULL;
+ Targetattrfilter *attrFilter;
+ int found = 0;
+
+ if (aclpb->aclpb_access & ACLPB_SLAPI_ACL_WRITE_ADD &&
+ aci->aci_type & ACI_TARGET_ATTR_ADD_FILTERS) {
+
+ attrFilterArray = aci->targetAttrAddFilters;
+
+ } else if (aclpb->aclpb_access & ACLPB_SLAPI_ACL_WRITE_DEL &&
+ aci->aci_type & ACI_TARGET_ATTR_DEL_FILTERS) {
+
+ attrFilterArray = aci->targetAttrDelFilters;
+
+ }
+
+
+ /*
+ * Scan this filter list for an applicable filter.
+ */
+
+ found = 0;
+ num_attrs = 0;
+
+ while (attrFilterArray[num_attrs] && !found) {
+ attrFilter = attrFilterArray[num_attrs];
+
+ /* If this filter applies to the attribute, stop. */
+ if ((aclpb->aclpb_curr_attrEval) &&
+ slapi_attr_type_cmp ( aclpb->aclpb_curr_attrEval->attrEval_name,
+ attrFilter->attr_str, 1) == 0) {
+ found = 1;
+ }
+ num_attrs++;
+ }
+
+ /*
+ * Here, if found an applicable filter, then apply the filter to the
+ * (attr,val) pair.
+ * Otherwise, ignore the targetattrfilters.
+ */
+
+ if (found) {
+
+ if ( acl__make_filter_test_entry(
+ &aclpb->aclpb_filter_test_entry,
+ aclpb->aclpb_curr_attrEval->attrEval_name,
+ aclpb->aclpb_curr_attrVal) == LDAP_SUCCESS ) {
+
+ attr_matched= acl__test_filter(aclpb->aclpb_filter_test_entry,
+ attrFilter->filter,
+ 1 /* Do filter sense evaluation below */
+ );
+ slapi_entry_free( aclpb->aclpb_filter_test_entry );
+ }
+
+ /* No need to look further */
+ if (attr_matched == ACL_FALSE) {
+ matches = attr_matched;
+ goto acl__resource_match_aci_EXIT;
+ }
+
+ /*
+ * Here this attribute appeared and was matched in a
+ * targetattrfilters list, so record this fact so we do
+ * not have to scan the targetattr list for the attribute.
+ */
+
+ attr_matched_in_targetattrfilters = 1;
+
+
+ }
+ } /* targetvaluefilters */
+
+
+ /* There are 3 cases by which acis are selected.
+ ** 1) By scanning the whole list and picking based on the resource.
+ ** 2) By picking a subset of the list which will be used for the whole
+ ** acl evaluation.
+ ** 3) A finer granularity, i.e, a selected list of acls which will be
+ ** used for only that entry's evaluation.
+ */
+ if ( !(skip_attrEval) && (aclpb->aclpb_state & ACLPB_SEARCH_BASED_ON_ENTRY_LIST) &&
+ (res_right & SLAPI_ACL_SEARCH) &&
+ ((aci->aci_access & SLAPI_ACL_READ) || (aci->aci_access & SLAPI_ACL_SEARCH))) {
+ int kk=0;
+
+ while ( kk < ACLPB_MAX_SELECTED_ACLS && aclpb->aclpb_handles_index[kk] >=0 ) kk++;
+ if (kk >= ACLPB_MAX_SELECTED_ACLS) {
+ aclpb->aclpb_state &= ~ACLPB_SEARCH_BASED_ON_ENTRY_LIST;
+ } else {
+ aclpb->aclpb_handles_index[kk++] = aci->aci_index;
+ aclpb->aclpb_handles_index[kk] = -1;
+ }
+ }
+
+
+ /* If we are suppose to skip attr eval, then let's skip it */
+ if ( (aclpb->aclpb_access & SLAPI_ACL_SEARCH ) && ( ! skip_attrEval ) &&
+ ( aclpb->aclpb_res_type & ACLPB_NEW_ENTRY )) {
+ aclEvalContext *c_evalContext = &aclpb->aclpb_curr_entryEval_context;
+ int nhandle = c_evalContext->acle_numof_tmatched_handles;
+
+ if ( nhandle < ACLPB_MAX_SELECTED_ACLS) {
+ c_evalContext->acle_handles_matched_target[nhandle] = aci->aci_index;
+ c_evalContext->acle_numof_tmatched_handles++;
+ }
+ }
+
+ if ( skip_attrEval ) {
+ goto acl__resource_match_aci_EXIT;
+ }
+
+ /* We need to check again because we don't want to select this handle
+ ** if the right doesn't match for now.
+ */
+ if (!(aci_right & res_right)) {
+ matches = ACL_FALSE;
+ goto acl__resource_match_aci_EXIT;
+ }
+
+ /*
+ * Here if the request is one that requires matching
+ * on a targetattr then do it here.
+ * If we have already matched an attribute in the targetattrfitlers list
+ * then we do not require a match in the targetattr so we can skip it.
+ * The operations that require targetattr are SLAPI_ACL_COMPARE,
+ * SLAPI_ACL_SEARCH, SLAPI_ACL_READ and SLAPI_ACL_WRITE, as long as
+ * c_attrEval is non-null (otherwise it's a modrdn op which
+ * does not require the targetattr list).
+ *
+ * rbyrneXXX if we had a proper permission for modrdn eg SLAPI_ACL_MODRDN
+ * then we would not need this crappy way of telling it was a MODRDN
+ * request ie. SLAPI_ACL_WRITE && !(c_attrEval).
+ */
+
+ c_attrEval = aclpb->aclpb_curr_attrEval;
+
+ /*
+ * If we've already matched on targattrfilter then do not
+ * bother to look at the attrlist.
+ */
+
+ if (!attr_matched_in_targetattrfilters) {
+
+ /* match target attr */
+ if ((c_attrEval) &&
+ (aci->aci_type & ACI_TARGET_ATTR)) {
+ /* there is a target ATTR */
+ Targetattr **attrArray = aci->targetAttr;
+ Targetattr *attr = NULL;
+
+ res_attr = c_attrEval->attrEval_name;
+ attr_matched = ACL_FALSE;
+ star_matched = ACL_FALSE;
+ num_attrs = 0;
+
+ while (attrArray[num_attrs] && !attr_matched) {
+ attr = attrArray[num_attrs];
+ if (attr->attr_type & ACL_ATTR_STRING) {
+ if (slapi_attr_type_cmp ( res_attr,
+ attr->u.attr_str, 1) == 0) {
+ attr_matched = ACL_TRUE;
+ *a_matched = ACL_TRUE;
+ }
+ } else if (attr->attr_type & ACL_ATTR_FILTER) {
+ if (ACL_TRUE == acl_match_substring (
+ attr->u.attr_filter,
+ res_attr, 1)) {
+ attr_matched = ACL_TRUE;
+ *a_matched = ACL_TRUE;
+ }
+ } else if (attr->attr_type & ACL_ATTR_STAR) {
+ attr_matched = ACL_TRUE;
+ *a_matched = ACL_TRUE;
+ star_matched = ACL_TRUE;
+ }
+ num_attrs++;
+ }
+
+ if (aci->aci_type & ACI_TARGET_ATTR_NOT) {
+ matches = (attr_matched ? ACL_FALSE : ACL_TRUE);
+ } else {
+ matches = (attr_matched ? ACL_TRUE: ACL_FALSE);
+ }
+
+
+ aclpb->aclpb_state &= ~ACLPB_ATTR_STAR_MATCHED;
+ /* figure out how it matched, i.e star matched */
+ if (matches && star_matched && num_attrs == 1 &&
+ !(aclpb->aclpb_state & ACLPB_FOUND_ATTR_RULE))
+ aclpb->aclpb_state |= ACLPB_ATTR_STAR_MATCHED;
+ else {
+ /* we are here means that there is a specific
+ ** attr in the rule for this resource.
+ ** We need to avoid this case
+ ** Rule 1: (targetattr = "uid")
+ ** Rule 2: (targetattr = "*")
+ ** we cannot use STAR optimization
+ */
+ aclpb->aclpb_state |= ACLPB_FOUND_ATTR_RULE;
+ aclpb->aclpb_state &= ~ACLPB_ATTR_STAR_MATCHED;
+ }
+ } else if ( (c_attrEval) ||
+ (aci->aci_type & ACI_TARGET_ATTR)) {
+ if ((aci_right & ACL_RIGHTS_TARGETATTR_NOT_NEEDED) &&
+ (aclpb->aclpb_access & ACL_RIGHTS_TARGETATTR_NOT_NEEDED)) {
+ /*
+ ** Targetattr rule doesn't make any sense
+ ** in this case. So select this rule
+ ** default: matches = ACL_TRUE;
+ */
+ ;
+ } else if (aci_right & SLAPI_ACL_WRITE &&
+ (aci->aci_type & ACI_TARGET_ATTR) &&
+ !(c_attrEval)) {
+ /* We need to handle modrdn operation. Modrdn doesn't
+ ** change any attrs but changes the RDN and so (attr=NULL).
+ ** Here we found an acl which has a targetattr but
+ ** the resource doesn't need one. In that case, we should
+ ** consider this acl.
+ ** default: matches = ACL_TRUE;
+ */
+ ;
+ } else {
+ matches = ACL_FALSE;
+ }
+ }
+ }/* !attr_matched_in_targetattrfilters */
+
+ /*
+ ** Here we are testing if we find a entry test rule (which should
+ ** be rare). In that case, just remember it. An entry test rule
+ ** doesn't have "(targetattr)".
+ */
+ if (aclpb && (aclpb->aclpb_state & ACLPB_EVALUATING_FIRST_ATTR) &&
+ (!(aci->aci_type & ACI_TARGET_ATTR))) {
+ aclpb->aclpb_state |= ACLPB_FOUND_A_ENTRY_TEST_RULE;
+ }
+
+ /*
+ * Generic exit point for this routine:
+ * matches is ACL_TRUE if the aci matches the target of the resource,
+ * ACL_FALSE othrewise.
+ * Apologies for the goto--this is a retro-fitted exit point.
+ */
+acl__resource_match_aci_EXIT:
+
+ /*
+ * For macro acis, there may be a partial macro string
+ * placed in the aclpb_macro_ht
+ * even if the aci did not finally match.
+ * All the partial strings will be freed at aclpb
+ * cleanup time.
+ */
+
+ TNF_PROBE_0_DEBUG(acl__resource_match_aci_end,"ACL","");
+
+ return (matches);
+}
+/* Macro to determine if the cached result is valid or not. */
+#define ACL_CACHED_RESULT_VALID( result) \
+ (((result & ACLPB_CACHE_READ_RES_ALLOW) && \
+ (result & ACLPB_CACHE_READ_RES_SKIP)) || \
+ ((result & ACLPB_CACHE_SEARCH_RES_ALLOW) && \
+ (result & ACLPB_CACHE_SEARCH_RES_SKIP))) ? 0 : 1
+/***************************************************************************
+*
+* acl__TestRights
+*
+* Test the rights and find out if access is allowed or not.
+*
+* Processing Alogorithm:
+*
+* First, process the DENY rules one by one. If the user is not explicitly
+* denied, then check if the user is allowed by processing the ALLOW handles
+* one by one.
+* The result of the evaluation is cached. Exceptions are
+* -- If an acl happens to be in both DENY and ALLOW camp.
+* -- Only interested for READ/SEARCH right.
+*
+* Input:
+* struct acl_pblock *aclpb - main acl private block
+* int access - The access bits
+* char **right - The right we are looking for
+* char ** generic - Generic rights
+*
+* Returns:
+*
+* ACL_RES_ALLOW - Access allowed
+* ACL_RES_DENY - Access denied
+* err - error condition
+*
+* Error Handling:
+* None.
+*
+**************************************************************************/
+static int
+acl__TestRights(Acl_PBlock *aclpb,int access, char **right, char ** map_generic,
+ aclResultReason_t *result_reason)
+{
+ ACLEvalHandle_t *acleval;
+ int rights_rv = ACL_RES_DENY;
+ int rv, i,j, k;
+ int index;
+ char *deny = NULL;
+ char *deny_generic = NULL;
+ char *acl_tag;
+ int expr_num;
+ char *testRights[2];
+ aci_t *aci;
+ int numHandles = 0;
+ aclEvalContext *c_evalContext = NULL;
+
+ TNF_PROBE_0_DEBUG(acl__TestRights_start,"ACL","");
+
+ c_evalContext = &aclpb->aclpb_curr_entryEval_context;
+
+ /* record the aci and reason for access decision */
+ result_reason->deciding_aci = NULL;
+ result_reason->reason = ACL_REASON_NONE;
+
+ /* If we don't have any ALLLOW handles, it's DENY by default */
+ if (aclpb->aclpb_num_allow_handles <= 0) {
+ result_reason->deciding_aci = NULL;
+ result_reason->reason = ACL_REASON_NO_MATCHED_RESOURCE_ALLOWS;
+
+ TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","",
+ tnf_string,no_allows,"");
+
+ return ACL_RES_DENY;
+ }
+
+ /* Get the ACL evaluation Context */
+ acleval = aclpb->aclpb_acleval;
+
+ testRights[0] = *right;
+ testRights[1] = '\0';
+
+ /*
+ ** START PROCESSING DENY HANDLES
+ ** Process each handle at a time. Do not concatenate the handles or else
+ ** all the context information will be build again and we will pay a
+ ** lot of penalty. The context is built the first time the handle is
+ ** processed.
+ **
+ ** First we set the default to INVALID so that if rules are not matched, then
+ ** we get INVALID and if a rule matched, the we get DENY.
+ */
+ aclpb->aclpb_state &= ~ACLPB_EXECUTING_ALLOW_HANDLES;
+ aclpb->aclpb_state |= ACLPB_EXECUTING_DENY_HANDLES;
+ ACL_SetDefaultResult (NULL, acleval, ACL_RES_INVALID);
+
+ numHandles = ACI_MAX_ELEVEL + aclpb->aclpb_num_deny_handles;
+ for (i=0, k=0; i < numHandles && k < aclpb->aclpb_num_deny_handles ; ++i) {
+ int skip_eval = 0;
+
+ /*
+ ** If the handle has been evaluated before, we can
+ ** cache the result.
+ */
+ if (((aci = aclpb->aclpb_deny_handles[i]) == NULL) && (i <= ACI_MAX_ELEVEL))
+ continue;
+ k++;
+ index = aci->aci_index;
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+ "Evaluating DENY aci(%d) \"%s\"\n", index, aci->aclName);
+
+ if (access & ( SLAPI_ACL_SEARCH | SLAPI_ACL_READ)) {
+
+ /*
+ * aclpb->aclpb_cache_result[0..aclpb->aclpb_last_cache_result] is
+ * a cache of info about whether applicable acis
+ * allowed, did_not_allow or denied access
+ */
+ for (j =0; j < aclpb->aclpb_last_cache_result; j++) {
+ if (index == aclpb->aclpb_cache_result[j].aci_index) {
+ short result;
+
+ result = aclpb->aclpb_cache_result[j].result;
+ if ( result <= 0) break;
+ if (!ACL_CACHED_RESULT_VALID(result)) {
+ /* something is wrong. Need to evaluate */
+ aclpb->aclpb_cache_result[j].result = -1;
+ break;
+ }
+ /*
+ ** We have a valid cached result. Let's see if we
+ ** have what we need.
+ */
+ if (access & SLAPI_ACL_SEARCH) {
+ if ( result & ACLPB_CACHE_SEARCH_RES_DENY){
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+ "DENY:Found SEARCH DENY in cache\n",0,0,0);
+ __acl_set_aclIndex_inResult ( aclpb, access, index );
+ result_reason->deciding_aci = aci;
+ result_reason->reason = ACL_REASON_RESULT_CACHED_DENY;
+ TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","",
+ tnf_string,cached_deny,"");
+ return ACL_RES_DENY;
+ } else if ((result & ACLPB_CACHE_SEARCH_RES_SKIP) ||
+ (result & ACLPB_CACHE_SEARCH_RES_ALLOW)) {
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+ "DENY:Found SEARCH SKIP in cache\n",0,0,0);
+ skip_eval = 1;
+ break;
+ } else {
+ break;
+ }
+ } else { /* must be READ */
+ if (result & ACLPB_CACHE_READ_RES_DENY) {
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+ "DENY:Found READ DENY in cache\n",0,0,0);
+ __acl_set_aclIndex_inResult ( aclpb, access, index );
+ result_reason->deciding_aci = aci;
+ result_reason->reason = ACL_REASON_RESULT_CACHED_DENY;
+ TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","",
+ tnf_string,cached_deny,"");
+ return ACL_RES_DENY;
+ } else if ( result & ACLPB_CACHE_READ_RES_SKIP) {
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+ "DENY:Found READ SKIP in cache\n",0,0,0);
+ skip_eval = 1;
+ break;
+ } else {
+ break;
+ }
+ }
+ }
+ }
+ }
+ if (skip_eval) {
+ skip_eval = 0;
+ continue;
+ }
+
+ rv = ACL_EvalSetACL(NULL, acleval, aci->aci_handle);
+ if ( rv < 0) {
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+ "acl__TestRights:Unable to set the DENY acllist\n",
+ 0,0,0);
+ continue;
+ }
+ /*
+ ** Now we have all the information we need. We need to call
+ ** the ONE ACL to test the rights.
+ ** return value: ACL_RES_DENY, ACL_RES_ALLOW, error codes
+ */
+ aclpb->aclpb_curr_aci = aci;
+ rights_rv = ACL_EvalTestRights (NULL, acleval, testRights,
+ map_generic, &deny,
+ &deny_generic,
+ &acl_tag, &expr_num);
+
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name, "Processed:%d DENY handles Result:%d\n",index, rights_rv,0);
+
+ if (rights_rv == ACL_RES_FAIL) {
+ result_reason->deciding_aci = aci;
+ result_reason->reason = ACL_REASON_NONE;
+ TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","",
+ tnf_string,evaled_deny,"");
+ return ACL_RES_DENY;
+ }
+
+ /* have we executed an ATTR RULE */
+ if ( aci->aci_ruleType & ACI_ATTR_RULES )
+ aclpb->aclpb_state |= ACLPB_ATTR_RULE_EVALUATED;
+
+ if (access & (SLAPI_ACL_SEARCH | SLAPI_ACL_READ)) {
+
+ for (j=0; j <aclpb->aclpb_last_cache_result; ++j) {
+ if (index == aclpb->aclpb_cache_result[j].aci_index) {
+ break;
+ }
+ }
+
+ if ( j < aclpb->aclpb_last_cache_result) {
+ /* already in cache */
+ } else if ( j < ACLPB_MAX_CACHE_RESULTS ) {
+ /* j == aclpb->aclpb_last_cache_result &&
+ j < ACLPB_MAX_CACHE_RESULTS */
+ aclpb->aclpb_last_cache_result++;
+ aclpb->aclpb_cache_result[j].aci_index = index;
+ aclpb->aclpb_cache_result[j].aci_ruleType = aci->aci_ruleType;
+
+ } else { /* cache overflow */
+ if ( rights_rv == ACL_RES_DENY) {
+ result_reason->deciding_aci = aci;
+ result_reason->reason = ACL_REASON_EVALUATED_DENY;
+ TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","",
+ tnf_string,evaled_deny,"");
+ return ACL_RES_DENY;
+ } else {
+ continue;
+ }
+ }
+
+ __acl_set_aclIndex_inResult ( aclpb, access, index );
+ if (rights_rv == ACL_RES_DENY) {
+ if (access & SLAPI_ACL_SEARCH) {
+ aclpb->aclpb_cache_result[j].result |=
+ ACLPB_CACHE_SEARCH_RES_DENY;
+ } else { /* MUST BE READ */
+ aclpb->aclpb_cache_result[j].result |=
+ ACLPB_CACHE_READ_RES_DENY;
+ }
+ /* We are done -- return */
+ result_reason->deciding_aci = aci;
+ result_reason->reason = ACL_REASON_EVALUATED_DENY;
+ TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","",
+ tnf_string,evaled_deny,"");
+ return ACL_RES_DENY;
+ } else if (rights_rv == ACL_RES_ALLOW) {
+ /* This will happen, of we have an acl with both deny and allow
+ ** Since we may not have finished all the deny acl, go thru all
+ ** of them. We will use this cached result when we evaluate this
+ ** handle in the context of allow handles.
+ */
+ if (access & SLAPI_ACL_SEARCH) {
+ aclpb->aclpb_cache_result[j].result |=
+ ACLPB_CACHE_SEARCH_RES_ALLOW;
+ } else {
+ aclpb->aclpb_cache_result[j].result |=
+ ACLPB_CACHE_READ_RES_ALLOW;
+ }
+
+ } else {
+ if (access & SLAPI_ACL_SEARCH) {
+ aclpb->aclpb_cache_result[j].result |=
+ ACLPB_CACHE_SEARCH_RES_SKIP;
+ } else {
+ aclpb->aclpb_cache_result[j].result |=
+ ACLPB_CACHE_READ_RES_SKIP;
+ }
+ continue;
+ }
+ } else {
+ if ( rights_rv == ACL_RES_DENY ) {
+ result_reason->deciding_aci = aci;
+ result_reason->reason = ACL_REASON_EVALUATED_DENY;
+ TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","",
+ tnf_string,evaled_deny,"");
+ return ACL_RES_DENY;
+ }
+ }
+ }
+
+
+ /*
+ ** START PROCESSING ALLOW HANDLES.
+ ** Process each handle at a time. Do not concatenate the handles or else
+ ** all the context information will be build again and we will pay a
+ ** lot of penalty. The context is built the first time the handle is
+ ** processed.
+ **
+ ** First we set the default to INVALID so that if rules are not matched, then
+ ** we get INVALID and if a rule matched, the we get ALLOW.
+ */
+ aclpb->aclpb_state &= ~ACLPB_EXECUTING_DENY_HANDLES;
+ aclpb->aclpb_state |= ACLPB_EXECUTING_ALLOW_HANDLES;
+ ACL_SetDefaultResult (NULL, acleval, ACL_RES_INVALID);
+ numHandles = ACI_MAX_ELEVEL + aclpb->aclpb_num_allow_handles;
+ for (i=0, k=0; i < numHandles && k < aclpb->aclpb_num_allow_handles ; ++i) {
+ int skip_eval = 0;
+ /*
+ ** If the handle has been evaluated before, we can
+ ** cache the result.
+ */
+ aci = aclpb->aclpb_allow_handles[i];
+ if (((aci = aclpb->aclpb_allow_handles[i]) == NULL) && (i <= ACI_MAX_ELEVEL))
+ continue;
+ k++;
+ index = aci->aci_index;
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+ "%d. Evaluating ALLOW aci(%d) \"%s\"\n", k, index, aci->aclName);
+
+ if (access & ( SLAPI_ACL_SEARCH | SLAPI_ACL_READ)) {
+
+ /*
+ * aclpb->aclpb_cache_result[0..aclpb->aclpb_last_cache_result] is
+ * a cache of info about whether applicable acis
+ * allowed, did_not_allow or denied access
+ */
+
+ for (j =0; j < aclpb->aclpb_last_cache_result; j++) {
+ if (index == aclpb->aclpb_cache_result[j].aci_index) {
+ short result;
+ result = aclpb->aclpb_cache_result[j].result;
+ if ( result <= 0) break;
+
+ if (!ACL_CACHED_RESULT_VALID(result)) {
+ /* something is wrong. Need to evaluate */
+ aclpb->aclpb_cache_result[j].result = -1;
+ break;
+ }
+
+ /*
+ ** We have a valid cached result. Let's see if we
+ ** have what we need.
+ */
+ if (access & SLAPI_ACL_SEARCH) {
+ if (result & ACLPB_CACHE_SEARCH_RES_ALLOW) {
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+ "Found SEARCH ALLOW in cache\n");
+ __acl_set_aclIndex_inResult ( aclpb, access, index );
+ result_reason->deciding_aci = aci;
+ result_reason->reason = ACL_REASON_RESULT_CACHED_ALLOW;
+ TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","",
+ tnf_string,cached_allow,"");
+ return ACL_RES_ALLOW;
+ } else if ( result & ACLPB_CACHE_SEARCH_RES_SKIP) {
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+ "Found SEARCH SKIP in cache\n",0,0,0);
+ skip_eval = 1;
+ break;
+ } else {
+ /* need to evaluate */
+ break;
+ }
+ } else {
+ if ( result & ACLPB_CACHE_READ_RES_ALLOW) {
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+ "Found READ ALLOW in cache\n",0,0,0);
+ __acl_set_aclIndex_inResult ( aclpb, access, index );
+ result_reason->deciding_aci = aci;
+ result_reason->reason = ACL_REASON_RESULT_CACHED_ALLOW;
+ TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","",
+ tnf_string,cached_allow,"");
+ return ACL_RES_ALLOW;
+ } else if ( result & ACLPB_CACHE_READ_RES_SKIP) {
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+ "Found READ SKIP in cache\n",0,0,0);
+ skip_eval = 1;
+ break;
+ } else {
+ break;
+ }
+ }
+ }
+ }
+ }
+ if ( skip_eval) {
+ skip_eval = 0;
+ continue;
+ }
+
+ TNF_PROBE_0_DEBUG(acl__libaccess_start,"ACL","");
+ rv = ACL_EvalSetACL(NULL, acleval, aci->aci_handle);
+ if ( rv < 0) {
+ slapi_log_error(SLAPI_LOG_FATAL, plugin_name,
+ "acl__TestRights:Unable to set the acllist\n",
+ 0,0,0);
+ continue;
+ }
+ /*
+ ** Now we have all the information we need. We need to call
+ ** the ONE ACL to test the rights.
+ ** return value: ACL_RES_DENY, ACL_RES_ALLOW, error codes
+ */
+ aclpb->aclpb_curr_aci = aci;
+ rights_rv = ACL_EvalTestRights (NULL, acleval, testRights,
+ map_generic, &deny,
+ &deny_generic,
+ &acl_tag, &expr_num);
+ TNF_PROBE_0_DEBUG(acl__libaccess_end,"ACL","");
+
+ if (aci->aci_ruleType & ACI_ATTR_RULES)
+ aclpb->aclpb_state |= ACLPB_ATTR_RULE_EVALUATED;
+
+ if (access & (SLAPI_ACL_SEARCH | SLAPI_ACL_READ)) {
+
+ for (j=0; j <aclpb->aclpb_last_cache_result; ++j) {
+ if (index == aclpb->aclpb_cache_result[j].aci_index) {
+ break;
+ }
+ }
+
+ if ( j < aclpb->aclpb_last_cache_result) {
+ /* already in cache */
+ } else if ( j < ACLPB_MAX_CACHE_RESULTS ) {
+ /* j == aclpb->aclpb_last_cache_result &&
+ j < ACLPB_MAX_CACHE_RESULTS */
+ aclpb->aclpb_last_cache_result++;
+ aclpb->aclpb_cache_result[j].aci_index = index;
+ aclpb->aclpb_cache_result[j].aci_ruleType = aci->aci_ruleType;
+ } else { /* cache overflow */
+ slapi_log_error (SLAPI_LOG_FATAL, "acl__TestRights", "cache overflown\n");
+ if ( rights_rv == ACL_RES_ALLOW) {
+ result_reason->deciding_aci = aci;
+ result_reason->reason = ACL_REASON_EVALUATED_ALLOW;
+ TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","",
+ tnf_string,evaled_allow,"");
+ return ACL_RES_ALLOW;
+ } else {
+ continue;
+ }
+ }
+
+ __acl_set_aclIndex_inResult ( aclpb, access, index );
+ if (rights_rv == ACL_RES_ALLOW) {
+ if (access & SLAPI_ACL_SEARCH) {
+ aclpb->aclpb_cache_result[j].result |=
+ ACLPB_CACHE_SEARCH_RES_ALLOW;
+ } else { /* must be READ */
+ aclpb->aclpb_cache_result[j].result |=
+ ACLPB_CACHE_READ_RES_ALLOW;
+ }
+
+ /* We are done -- return */
+ result_reason->deciding_aci = aci;
+ result_reason->reason = ACL_REASON_EVALUATED_ALLOW;
+ TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","",
+ tnf_string,evaled_allow,"");
+ return ACL_RES_ALLOW;
+
+ } else {
+ if (access & SLAPI_ACL_SEARCH) {
+ aclpb->aclpb_cache_result[j].result |=
+ ACLPB_CACHE_SEARCH_RES_SKIP;
+ } else {
+ aclpb->aclpb_cache_result[j].result |=
+ ACLPB_CACHE_READ_RES_SKIP;
+ }
+ continue;
+ }
+ } else {
+ if ( rights_rv == ACL_RES_ALLOW ) {
+ result_reason->deciding_aci = aci;
+ result_reason->reason = ACL_REASON_EVALUATED_ALLOW;
+ TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","",
+ tnf_string,evaled_allow,"");
+ return ACL_RES_ALLOW;
+ }
+ }
+ }/* for */
+ result_reason->deciding_aci = aci;
+ result_reason->reason = ACL_REASON_NO_MATCHED_SUBJECT_ALLOWS;
+
+ TNF_PROBE_0_DEBUG(acl__TestRights_end,"ACL","");
+
+ return (ACL_RES_DENY);
+}
+/***************************************************************************
+*
+* acl_match_substring
+*
+* Compare the input string to the patteren in the filter
+*
+* Input:
+* struct slapi_filter *f - Filter which has the patteren
+* char *str - String to compare
+* int exact_match - 1; match the pattern exactly
+* - 0; match the pattern as a suffix
+*
+* Returns:
+* ACL_TRUE - The sting matches with the patteren
+* ACL_FALSE - No it doesn't
+* ACL_ERR - Error
+*
+* Error Handling:
+* None.
+*
+**************************************************************************/
+int
+acl_match_substring ( Slapi_Filter *f, char *str, int exact_match)
+{
+ int i, rc, len;
+ char *p;
+ char *end, *realval, *tmp;
+ char pat[BUFSIZ];
+ char buf[BUFSIZ];
+ char *type, *initial, *final;
+ char **any;
+
+ if ( 0 != slapi_filter_get_subfilt ( f, &type, &initial, &any, &final ) ) {
+ return (ACL_FALSE);
+ }
+
+ /* convert the input to lower. */
+ for (p = str; *p; p++)
+ *p = TOLOWER ( *p );
+
+ /* construct a regular expression corresponding to the filter: */
+ pat[0] = '\0';
+ p = pat;
+ end = pat + sizeof(pat) - 2; /* leave room for null */
+
+
+ if ( initial != NULL) {
+ strcpy (p, "^");
+ p = strchr (p, '\0');
+
+ /* 2 * in case every char is special */
+ if (p + 2 * strlen ( initial ) > end) {
+ slapi_log_error (SLAPI_LOG_ACL, plugin_name,
+ "not enough pattern space\n", 0, 0, 0);
+
+ return (ACL_ERR);
+ }
+
+ if (!exact_match) {
+ strcpy (p, ".*");
+ p = strchr (p, '\0');
+ }
+ acl_strcpy_special (p, initial);
+ p = strchr (p, '\0');
+ }
+
+ if ( any != NULL) {
+ for (i = 0; any && any[i] != NULL; i++) {
+ /* ".*" + value */
+ if (p + 2 * strlen ( any[i]) + 2 > end) {
+ slapi_log_error (SLAPI_LOG_ACL, plugin_name,
+ "not enough pattern space\n", 0, 0, 0);
+ return (ACL_ERR);
+ }
+
+ strcpy (p, ".*");
+ p = strchr (p, '\0');
+ acl_strcpy_special (p, any[i]);
+ p = strchr (p, '\0');
+ }
+ }
+
+
+ if ( final != NULL) {
+ /* ".*" + value */
+ if (p + 2 * strlen ( final ) + 2 > end) {
+ slapi_log_error (SLAPI_LOG_ACL, plugin_name,
+ "not enough pattern space\n", 0, 0, 0);
+ return (ACL_ERR);
+ }
+
+ strcpy (p, ".*");
+ p = strchr (p, '\0');
+ acl_strcpy_special (p, final);
+ p = strchr (p, '\0');
+ strcpy (p, "$");
+ }
+
+ /* see if regex matches with the input string */
+ tmp = NULL;
+ len = strlen(str);
+
+ if (len < sizeof(buf)) {
+ strcpy (buf, str);
+ realval = buf;
+ } else {
+ tmp = (char*) slapi_ch_malloc (len + 1);
+ strcpy (tmp, str);
+ realval = tmp;
+ }
+
+ slapi_dn_normalize (realval);
+
+
+ /* What we have built is a regular pattaren expression.
+ ** Now we will compile the pattern and compare wth the string to
+ ** see if the input string matches with the patteren or not.
+ */
+ slapd_re_lock();
+ if ((p = slapd_re_comp (pat)) != 0) {
+ slapi_log_error (SLAPI_LOG_ACL, plugin_name,
+ "acl_match_substring:re_comp failed (%s)\n", p, 0, 0);
+ slapd_re_unlock();
+ return (ACL_ERR);
+ }
+
+ /* re_exec() returns 1 if the string p1 matches the last compiled
+ ** regular expression, 0 if the string p1 failed to match
+ ** (see man pages)
+ **
+ ** IMPORTANT NOTE: If I use compile() and step() to do the patteren
+ ** matching, it seems that step() is leaking 1036 bytes/search
+ ** I couldn't figure out why it's leaking.
+ */
+ rc = slapd_re_exec( realval );
+
+ slapd_re_unlock();
+
+ if (tmp != NULL) {
+ slapi_ch_free ( (void **) &tmp );
+ }
+
+ if (rc == 1) {
+ return ACL_TRUE;
+ } else {
+ return ACL_FALSE;
+ }
+}
+/***************************************************************************
+*
+* acl__reset_cached_result
+*
+* Go thru the cached result and invlalidate the cached evaluation results for
+* rules which can only be cached based on the scope.
+* If we have scope ACI_CACHE_RESULT_PER_ENTRY, then we need to invalidate the
+* cacched reult whenever we hit a new entry.
+*
+* Returns:
+* Null
+*
+**************************************************************************/
+static void
+acl__reset_cached_result (struct acl_pblock *aclpb )
+{
+
+ int j;
+
+ for (j =0; j < aclpb->aclpb_last_cache_result; j++) {
+ /* Unless we have to clear the result, move on */
+ if (!( aclpb->aclpb_cache_result[j].aci_ruleType & ACI_CACHE_RESULT_PER_ENTRY))
+ continue;
+ aclpb->aclpb_cache_result[j].result = 0;
+ }
+}
+/*
+ * acl_access_allowed_disjoint_resource
+ *
+ * This is an internal module which can be used to verify
+ * access to a resource which may not be inside the search scope.
+ *
+ * Returns:
+ * - Same return val as acl_access_allowed().
+ */
+int
+acl_access_allowed_disjoint_resource(
+ Slapi_PBlock *pb,
+ Slapi_Entry *e, /* The Slapi_Entry */
+ char *attr, /* Attribute of the entry */
+ struct berval *val, /* value of attr. NOT USED */
+ int access /* access rights */
+ )
+{
+
+ int rv;
+ struct acl_pblock *aclpb = NULL;
+
+ aclpb = acl_get_aclpb ( pb, ACLPB_BINDDN_PBLOCK );
+ /*
+ ** It's possible that we already have a context of ACLs.
+ ** However once in a while, we need to test
+ ** access to a resource which (like vlv, schema) which falls
+ ** outside the search base. In that case, we need to go
+ ** thru all the ACLs and not depend upon the acls which we have
+ ** gathered.
+ */
+
+ /* If you have the right to use the resource, then we don't need to check for
+ ** proxy right on that resource.
+ */
+ if (aclpb)
+ aclpb->aclpb_state |= ( ACLPB_DONOT_USE_CONTEXT_ACLS| ACLPB_DONOT_EVALUATE_PROXY );
+
+ rv = acl_access_allowed(pb, e, attr, val, access);
+
+ if (aclpb) aclpb->aclpb_state &= ~ACLPB_DONOT_USE_CONTEXT_ACLS;
+ if (aclpb ) aclpb->aclpb_state &= ~ACLPB_DONOT_EVALUATE_PROXY;
+
+ return rv;
+}
+
+/*
+ * acl__attr_cached_result
+ * Loops thru the cached result and determines if we can use the cached value.
+ *
+ * Inputs:
+ * Slapi_pblock *aclpb - acl private block
+ * char *attr - attribute name
+ * int access - access type
+ * Returns:
+ * LDAP_SUCCESS: - access is granted
+ * LDAP_INSUFFICIENT_ACCESS - access denied
+ * ACL_ERR - no cached info about this attr.
+ * - or the attr had multiple result and so
+ * - we can't determine the outcome.
+ *
+ */
+static int
+acl__attr_cached_result (struct acl_pblock *aclpb, char *attr, int access )
+{
+
+ int i, rc;
+ aclEvalContext *c_evalContext;
+
+ if ( !(access & ( SLAPI_ACL_SEARCH | SLAPI_ACL_READ) ))
+ return ACL_ERR;
+
+ if (aclpb->aclpb_state & ACLPB_HAS_ACLCB_EVALCONTEXT ) {
+ c_evalContext = &aclpb->aclpb_prev_opEval_context;
+ slapi_log_error ( SLAPI_LOG_ACL, plugin_name,
+ "acl__attr_cached_result:Using Context: ACLPB_ACLCB\n", 0,0,0 );
+ } else {
+ c_evalContext = &aclpb->aclpb_prev_entryEval_context;
+ slapi_log_error ( SLAPI_LOG_ACL, plugin_name,
+ "acl__attr_cached_result:Using Context: ACLPB_PREV\n", 0,0,0 );
+ }
+
+ if ( attr == NULL ) {
+ int eval_read = 0;
+ /*
+ ** Do I have access to at least one attribute, then I have
+ ** access to the entry.
+ */
+ for (i=0; i < c_evalContext->acle_numof_attrs; i++ ) {
+ AclAttrEval *a_eval = &c_evalContext->acle_attrEval[i];
+
+ if ( (access & SLAPI_ACL_READ ) && a_eval->attrEval_r_status &&
+ a_eval->attrEval_r_status < ACL_ATTREVAL_DETERMINISTIC ) {
+ eval_read++;
+ if ( a_eval->attrEval_r_status & ACL_ATTREVAL_SUCCESS)
+ return LDAP_SUCCESS;
+ /* rbyrne: recompute if we have to.
+ * How does this cached result get turned off for
+ * attr style acis which acannot be cached becuase entry
+ * can result in a diff value.
+ */
+ else if ( a_eval->attrEval_r_status & ACL_ATTREVAL_RECOMPUTE ) {
+ rc = acl__recompute_acl ( aclpb, a_eval, access,
+ a_eval->attrEval_r_aciIndex);
+ if ( rc != ACL_ERR ) {
+ acl_copyEval_context ( aclpb, c_evalContext,
+ &aclpb->aclpb_curr_entryEval_context, 1);
+ }
+ if ( rc == LDAP_SUCCESS) {
+ return LDAP_SUCCESS;
+ }
+ }
+ }
+ }/* for */
+ /*
+ * If we have scanned the whole list without success then
+ * we are not granting access to this entry through access
+ * to an attribute in the list--however this does not mean
+ * that we do not have access to the entry via another attribute
+ * not already in the list, so return -1 meaning
+ * "don't know".
+ */
+ return(ACL_ERR);
+#if 0
+ if ( eval_read )
+ return LDAP_INSUFFICIENT_ACCESS;
+ else
+ return ACL_ERR;
+#endif
+ }
+
+ for (i=0; i < c_evalContext->acle_numof_attrs; i++ ) {
+ AclAttrEval *a_eval = &c_evalContext->acle_attrEval[i];
+
+ if ( a_eval == NULL ) continue;
+
+ if (strcasecmp ( attr, a_eval->attrEval_name ) == 0 ) {
+ if ( access & SLAPI_ACL_SEARCH ) {
+ if (a_eval->attrEval_s_status < ACL_ATTREVAL_DETERMINISTIC ) {
+ if ( a_eval->attrEval_s_status & ACL_ATTREVAL_SUCCESS)
+ return LDAP_SUCCESS;
+ else if ( a_eval->attrEval_s_status & ACL_ATTREVAL_FAIL)
+ return LDAP_INSUFFICIENT_ACCESS;
+ else if ( a_eval->attrEval_s_status & ACL_ATTREVAL_RECOMPUTE ) {
+ rc = acl__recompute_acl ( aclpb, a_eval, access,
+ a_eval->attrEval_s_aciIndex);
+ if ( rc != ACL_ERR ) {
+ acl_copyEval_context ( aclpb, c_evalContext,
+ &aclpb->aclpb_curr_entryEval_context, 1);
+ }
+ } else
+ return ACL_ERR;
+ } else {
+ /* This means that for the same attribute and same type of
+ ** access, we had different results at different time.
+ ** Since we are not caching per object, we can't
+ ** determine exactly. So, can't touch this
+ */
+ return ACL_ERR;
+ }
+ } else {
+ if (a_eval->attrEval_r_status < ACL_ATTREVAL_DETERMINISTIC ) {
+ if ( a_eval->attrEval_r_status & ACL_ATTREVAL_SUCCESS)
+ return LDAP_SUCCESS;
+ else if ( a_eval->attrEval_r_status & ACL_ATTREVAL_FAIL)
+ return LDAP_INSUFFICIENT_ACCESS;
+ else if ( a_eval->attrEval_r_status & ACL_ATTREVAL_RECOMPUTE ) {
+ rc = acl__recompute_acl ( aclpb, a_eval, access,
+ a_eval->attrEval_r_aciIndex);
+ if ( rc != ACL_ERR ) {
+ acl_copyEval_context ( aclpb, c_evalContext,
+ &aclpb->aclpb_curr_entryEval_context, 1);
+ }
+ } else
+ return ACL_ERR;
+ } else {
+ /* Look above for explanation */
+ return ACL_ERR;
+ }
+ }
+ }
+ }
+ return ACL_ERR;
+}
+
+/*
+ * Had to do this juggling of casting to make
+ * both Nt & unix compiler happy.
+ */
+static int
+acl__cmp(const void *a, const void *b)
+{
+ short *i = (short *) a;
+ short *j = (short *) b;
+
+ if ( (short) *i > (short) *j )
+ return (1);
+ if ( (short)*i < (short) *j)
+ return (-1);
+ return (0);
+}
+
+/*
+ * acl__scan_match_handles
+ * Go thru the ACL list and determine if the list of acls selected matches
+ * what we have in the cache.
+ *
+ * Inputs:
+ * Acl_PBlock *pb - Main pblock ( blacvk hole)
+ * int type - Which context to look on
+ *
+ * Returns:
+ * 0 - matches all the acl handles
+ * ACL_ERR - sorry; no match
+ *
+ * ASSUMPTION: A READER LOCK ON ACL LIST
+ */
+static int
+acl__scan_match_handles ( Acl_PBlock *aclpb, int type)
+{
+
+
+ int matched = 0;
+ aci_t *aci = NULL;
+ int index;
+ PRUint32 cookie;
+ aclEvalContext *c_evalContext = NULL;
+
+ if (type == ACLPB_EVALCONTEXT_PREV ) {
+ c_evalContext = &aclpb->aclpb_prev_entryEval_context;
+ } else if ( type == ACLPB_EVALCONTEXT_ACLCB ){
+ c_evalContext = &aclpb->aclpb_prev_opEval_context;
+ } else {
+ return ACL_ERR;
+ }
+
+
+ if ( !c_evalContext->acle_numof_tmatched_handles )
+ return ACL_ERR;
+
+ aclpb->aclpb_stat_acllist_scanned++;
+ aci = acllist_get_first_aci ( aclpb, &cookie );
+
+ while ( aci ) {
+ index = aci->aci_index;
+ if (acl__resource_match_aci(aclpb, aci, 1 /* skip attr matching */, NULL )) {
+ int j;
+ int s_matched = matched;
+
+ /* We have a sorted list of handles that matched the target */
+
+ for (j=0; j < c_evalContext->acle_numof_tmatched_handles ; j++ ) {
+ if ( c_evalContext->acle_handles_matched_target[j] > index )
+
+ break;
+ else if ( index == c_evalContext->acle_handles_matched_target[j] ) {
+ int jj;
+ matched++;
+
+ /* See if this is a ATTR rule that matched -- in that case we have
+ ** to nullify the cached result
+ */
+ if ( aci->aci_ruleType & ACI_ATTR_RULES ) {
+ slapi_log_error ( SLAPI_LOG_ACL, plugin_name,
+ "Found an attr Rule [Name:%s Index:%d\n", aci->aclName,
+ aci->aci_index );
+ for ( jj =0; jj < c_evalContext->acle_numof_attrs; jj++ ) {
+ AclAttrEval *a_eval = &c_evalContext->acle_attrEval[jj];
+ if ( a_eval->attrEval_r_aciIndex == aci->aci_index )
+ a_eval->attrEval_r_status = ACL_ATTREVAL_RECOMPUTE;
+ if ( a_eval->attrEval_s_aciIndex == aci->aci_index )
+ a_eval->attrEval_s_status = ACL_ATTREVAL_RECOMPUTE;
+ }
+ }
+ break;
+ }
+ }
+ if ( s_matched == matched ) return ACL_ERR;
+ }
+ aci = acllist_get_next_aci ( aclpb, aci, &cookie );
+ }
+ if ( matched == c_evalContext->acle_numof_tmatched_handles )
+ return 0;
+
+ return ACL_ERR;
+}
+/*
+ * acl_copyEval_context
+ * Copy the context info which include attr info and handles.
+ *
+ * Inputs
+ * struct acl_pblock *aclpb - acl private main block
+ * aclEvalContext *src - src context
+ * aclEvalContext *dest - dest context
+ * Returns:
+ * None.
+ *
+ */
+void
+acl_copyEval_context ( struct acl_pblock *aclpb, aclEvalContext *src,
+ aclEvalContext *dest , int copy_attr_only )
+{
+
+ int d_slot, i;
+
+ /* Do a CLEAN copy we have nothing or else do an incremental copy.*/
+ if ( src->acle_numof_attrs < 1 )
+ return;
+
+ /* Copy the attr info */
+ if ( dest->acle_numof_attrs < 1 )
+ acl_clean_aclEval_context ( dest, 0 /*clean */ );
+
+ d_slot = dest->acle_numof_attrs;
+ for (i=0; i < src->acle_numof_attrs; i++ ) {
+ int j;
+ int attr_exists = 0;
+ int dd_slot = d_slot;
+
+ if ( aclpb && (i == 0) ) aclpb->aclpb_stat_num_copycontext++;
+
+ if ( src->acle_attrEval[i].attrEval_r_status == 0 &&
+ src->acle_attrEval[i].attrEval_s_status == 0 )
+ continue;
+
+ for ( j = 0; j < dest->acle_numof_attrs; j++ ) {
+ if ( strcasecmp ( src->acle_attrEval[i].attrEval_name,
+ dest->acle_attrEval[j].attrEval_name ) == 0 ) {
+ /* We have it. skip it. */
+ attr_exists = 1;
+ dd_slot = j;
+ break;
+ }
+ }
+ if ( !attr_exists ) {
+ if ( dd_slot >= ACLPB_MAX_ATTRS -1 )
+ break;
+
+ if ( aclpb) aclpb->aclpb_stat_num_copy_attrs++;
+
+ if ( dest->acle_attrEval[dd_slot].attrEval_name )
+ slapi_ch_free ( (void **) &dest->acle_attrEval[dd_slot].attrEval_name );
+
+ dest->acle_attrEval[dd_slot].attrEval_name =
+ slapi_ch_strdup ( src->acle_attrEval[i].attrEval_name );
+ }
+ /* Copy the result status and the aci index */
+ dest->acle_attrEval[dd_slot].attrEval_r_status =
+ src->acle_attrEval[i].attrEval_r_status;
+ dest->acle_attrEval[dd_slot].attrEval_r_aciIndex =
+ src->acle_attrEval[i].attrEval_r_aciIndex;
+ dest->acle_attrEval[dd_slot].attrEval_s_status =
+ src->acle_attrEval[i].attrEval_s_status;
+ dest->acle_attrEval[dd_slot].attrEval_s_aciIndex =
+ src->acle_attrEval[i].attrEval_s_aciIndex;
+
+ if (!attr_exists ) d_slot++;
+ }
+
+ dest->acle_numof_attrs = d_slot;
+ dest->acle_attrEval[d_slot].attrEval_name = NULL;
+
+ if ( copy_attr_only )
+ return;
+
+ /* First sort the arrays which keeps the acl index numbers */
+ qsort ( (char *) src->acle_handles_matched_target,
+ (size_t)src->acle_numof_tmatched_handles, sizeof( int ), acl__cmp );
+
+ for (i=0; i < src->acle_numof_tmatched_handles; i++ ) {
+ dest->acle_handles_matched_target[i] =
+ src->acle_handles_matched_target[i];
+ }
+
+ if ( src->acle_numof_tmatched_handles ) {
+ dest->acle_numof_tmatched_handles = src->acle_numof_tmatched_handles;
+ if ( aclpb) aclpb->aclpb_stat_num_tmatched_acls = src->acle_numof_tmatched_handles;
+ }
+}
+
+/*
+ * acl_clean_aclEval_context
+ * Clean the eval context
+ *
+ * Inputs:
+ * aclEvalContext *clean_me - COntext to be cleaned
+ * int clean_type - 0: clean, 1 scrub
+ *
+ */
+
+void
+acl_clean_aclEval_context ( aclEvalContext *clean_me, int scrub_only )
+{
+ int i;
+
+ /* Copy the attr info */
+ for (i=0; i < clean_me->acle_numof_attrs; i++ ) {
+
+ char *a_name = clean_me->acle_attrEval[i].attrEval_name;
+ if ( a_name && !scrub_only) {
+ slapi_ch_free ( (void **) &a_name );
+ clean_me->acle_attrEval[i].attrEval_name = NULL;
+ }
+ clean_me->acle_attrEval[i].attrEval_r_status = 0;
+ clean_me->acle_attrEval[i].attrEval_s_status = 0;
+ clean_me->acle_attrEval[i].attrEval_r_aciIndex = 0;
+ clean_me->acle_attrEval[i].attrEval_s_aciIndex = 0;
+ }
+
+ if ( !scrub_only ) clean_me->acle_numof_attrs = 0;
+ clean_me->acle_numof_tmatched_handles = 0;
+}
+/*
+ * acl__match_handlesFromCache
+ *
+ * We have 2 cacheed information
+ * 1) cached info from the previous operation
+ * 2) cached info from the prev entry evaluation
+ *
+ * What we are doing here is going thru all the acls and see if the same
+ * set of acls apply to this resource or not. If it does, then do we have
+ * a cached info for the attr. If we don't for both of the cases then we need
+ * to evaluate all over again.
+ *
+ * Inputs:
+ * struct acl_pblock - ACL private block;
+ * char *attr - Attribute name
+ * int access - acces type
+ *
+ * returns:
+ * LDAP_SUCCESS (0) - The same acls apply and we have
+ * access ALLOWED on the attr
+ * LDAP_INSUFFICIENT_ACCESS - The same acls apply and we have
+ * access DENIED on the attr
+ * -1 - Acls doesn't match or we don't have
+ * cached info for this attr.
+ *
+ * ASSUMPTIONS: A reader lock has been obtained for the acl list.
+ */
+static int
+acl__match_handlesFromCache ( Acl_PBlock *aclpb, char *attr, int access)
+{
+
+ aclEvalContext *c_evalContext = NULL;
+ int context_type = 0;
+ int ret_val = -1; /* it doen't match by default */
+
+ /* Before we proceed, find out if we have evaluated any ATTR RULE. If we have
+ ** then we can't use any caching mechanism
+ */
+
+ if ( aclpb->aclpb_state & ACLPB_HAS_ACLCB_EVALCONTEXT ) {
+ context_type = ACLPB_EVALCONTEXT_ACLCB;
+ c_evalContext = &aclpb->aclpb_prev_opEval_context;
+ } else {
+ context_type = ACLPB_EVALCONTEXT_PREV;
+ c_evalContext = &aclpb->aclpb_prev_entryEval_context;
+ }
+
+
+ if ( aclpb->aclpb_res_type & (ACLPB_NEW_ENTRY | ACLPB_EFFECTIVE_RIGHTS) ) {
+ aclpb->aclpb_state |= ACLPB_MATCHES_ALL_ACLS;
+ ret_val = acl__scan_match_handles ( aclpb, context_type );
+ if ( -1 == ret_val ) {
+ aclpb->aclpb_state &= ~ACLPB_MATCHES_ALL_ACLS;
+ aclpb->aclpb_state |= ACLPB_UPD_ACLCB_CACHE;
+ /* Did not match */
+ if ( context_type == ACLPB_HAS_ACLCB_EVALCONTEXT ) {
+ aclpb->aclpb_state &= ~ACLPB_HAS_ACLCB_EVALCONTEXT;
+ } else {
+ aclpb->aclpb_state |= ACLPB_COPY_EVALCONTEXT;
+ c_evalContext->acle_numof_tmatched_handles = 0;
+ }
+ }
+ }
+ if ( aclpb->aclpb_state & ACLPB_MATCHES_ALL_ACLS ) {
+ /* See if we have a cached result for this attr */
+ ret_val = acl__attr_cached_result (aclpb, attr, access);
+
+ /* It's not in the ACLCB context but we might have it in the
+ ** current/prev context. Take a look at it. we might have evaluated
+ ** this attribute already.
+ */
+ if ( (-1 == ret_val ) &&
+ ( aclpb->aclpb_state & ACLPB_HAS_ACLCB_EVALCONTEXT )) {
+ aclpb->aclpb_state &= ~ACLPB_HAS_ACLCB_EVALCONTEXT ;
+ ret_val = acl__attr_cached_result (aclpb, attr, access);
+ aclpb->aclpb_state |= ACLPB_HAS_ACLCB_EVALCONTEXT ;
+
+ /* We need to do an incremental update */
+ if ( !ret_val ) aclpb->aclpb_state |= ACLPB_INCR_ACLCB_CACHE;
+ }
+ }
+ return ret_val;
+}
+/*
+ * acl__get_attrEval
+ * Get the atteval from the current context and hold the ptr in aclpb.
+ * If we have too many attrs, then allocate a new one. In that case
+ * we let the caller know about that so that it will be deallocated.
+ *
+ * Returns:
+ * int - 0: The context was indexed. So, no allocations.
+ * - 1; context was allocated - deallocate it.
+ */
+static int
+acl__get_attrEval ( struct acl_pblock *aclpb, char *attr )
+{
+
+ int j;
+ aclEvalContext *c_ContextEval = &aclpb->aclpb_curr_entryEval_context;
+ int deallocate_attrEval = 0;
+ AclAttrEval *c_attrEval = NULL;
+
+ if ( !attr ) return deallocate_attrEval;
+
+ aclpb->aclpb_curr_attrEval = NULL;
+
+ /* Go thru and see if we have the attr already */
+ for (j=0; j < c_ContextEval->acle_numof_attrs; j++) {
+ c_attrEval = &c_ContextEval->acle_attrEval[j];
+
+ if ( c_attrEval &&
+ slapi_attr_type_cmp ( c_attrEval->attrEval_name, attr, 1) == 0 ) {
+ aclpb->aclpb_curr_attrEval = c_attrEval;
+ break;
+ }
+ }
+
+ if ( !aclpb->aclpb_curr_attrEval) {
+ if ( c_ContextEval->acle_numof_attrs == ACLPB_MAX_ATTRS -1 ) {
+ /* Too many attrs. create a temp one */
+ c_attrEval = (AclAttrEval * ) slapi_ch_calloc ( 1, sizeof ( AclAttrEval ) );
+ deallocate_attrEval =1;
+ } else {
+ c_attrEval = &c_ContextEval->acle_attrEval[c_ContextEval->acle_numof_attrs++];
+ c_attrEval->attrEval_r_status = 0;
+ c_attrEval->attrEval_s_status = 0;
+ c_attrEval->attrEval_r_aciIndex = 0;
+ c_attrEval->attrEval_s_aciIndex = 0;
+ }
+ /* clean it before use */
+ c_attrEval->attrEval_name = slapi_ch_strdup ( attr );
+ aclpb->aclpb_curr_attrEval = c_attrEval;
+ }
+ return deallocate_attrEval;
+}
+/*
+ * acl_skip_access_check
+ *
+ * See if we need to go thru the ACL check or not. We don't need to if I am root
+ * or internal operation or ...
+ *
+ * returns:
+ * ACL_TRUE - Yes; skip the ACL check
+ * ACL_FALSE - No; you have to go thru ACL check
+ *
+ */
+int
+acl_skip_access_check ( Slapi_PBlock *pb, Slapi_Entry *e )
+{
+ int rv, isRoot, accessCheckDisabled;
+ void *conn = NULL;
+ Slapi_Backend *be;
+
+ slapi_pblock_get ( pb, SLAPI_REQUESTOR_ISROOT, &isRoot );
+ if ( isRoot ) return ACL_TRUE;
+
+ /* See if this is local request */
+ slapi_pblock_get ( pb, SLAPI_CONNECTION, &conn);
+
+ if ( NULL == conn ) return ACL_TRUE;
+
+ /*
+ * Turn on access checking in the rootdse--this code used
+ * to skip the access check.
+ *
+ * check if the entry is the RootDSE entry
+ if ( e ) {
+ char * edn = slapi_entry_get_ndn ( e );
+ if ( slapi_is_rootdse ( edn ) ) return ACL_TRUE;
+ }
+ */
+
+ /* GB : when referrals are directly set in the mappin tree
+ * we can reach this code without a backend in the pblock
+ * in such a case, allow access for now
+ * we may want to reconsider this is NULL DSE implementation happens
+ */
+ rv = slapi_pblock_get ( pb, SLAPI_BACKEND, &be );
+ if (be == NULL)
+ return ACL_TRUE;
+
+ rv = slapi_pblock_get ( pb, SLAPI_PLUGIN_DB_NO_ACL, &accessCheckDisabled );
+ if ( rv != -1 && accessCheckDisabled ) return ACL_TRUE;
+
+ return ACL_FALSE;
+}
+short
+acl_get_aclsignature ()
+{
+ return acl_signature;
+}
+void
+acl_set_aclsignature ( short value)
+{
+ acl_signature = value;
+}
+void
+acl_regen_aclsignature ()
+{
+ acl_signature = aclutil_gen_signature ( acl_signature );
+}
+
+
+
+static int
+acl__handle_config_entry (Slapi_Entry *e, void *callback_data )
+{
+
+ int *value = (int *) callback_data;
+
+ *value = slapi_entry_attr_get_int( e, "nsslapd-readonly");
+
+ return 0;
+}
+
+static int
+acl__config_get_readonly ()
+{
+
+ int readonly = 0;
+
+ slapi_search_internal_callback( "cn=config", LDAP_SCOPE_BASE, "(objectclass=*)",
+ NULL, 0 /* attrsonly */,
+ &readonly/* callback_data */,
+ NULL /* controls */,
+ NULL /* result_callback */,
+ acl__handle_config_entry,
+ NULL /* referral_callback */);
+
+ return readonly;
+}
+/*
+*
+* Assumptions:
+* 1) Called for read/search right.
+*/
+static int
+acl__recompute_acl ( Acl_PBlock *aclpb,
+ AclAttrEval *a_eval,
+ int access,
+ int aciIndex
+ )
+{
+
+
+ char *unused_str1, *unused_str2;
+ char *acl_tag, *testRight[2];
+ int j, expr_num;
+ int result_status, rv, cache_result;
+ PRUint32 cookie;
+ aci_t *aci;
+
+
+ PR_ASSERT ( aciIndex >= 0 );
+ PR_ASSERT ( a_eval != NULL );
+ PR_ASSERT (aclpb != NULL );
+
+
+ /* We might have evaluated this acl just now, check it there first */
+
+ for ( j =0; j < aclpb->aclpb_last_cache_result; j++) {
+ if (aciIndex == aclpb->aclpb_cache_result[j].aci_index) {
+ short result;
+ result_status =ACL_RES_INVALID;
+
+ result = aclpb->aclpb_cache_result[j].result;
+ if ( result <= 0) break;
+ if (!ACL_CACHED_RESULT_VALID(result)) {
+ /* something is wrong. Need to evaluate */
+ aclpb->aclpb_cache_result[j].result = -1;
+ break;
+ }
+
+
+ /*
+ ** We have a valid cached result. Let's see if we
+ ** have what we need.
+ */
+ if ((result & ACLPB_CACHE_SEARCH_RES_ALLOW) ||
+ (result & ACLPB_CACHE_READ_RES_ALLOW) )
+ result_status = ACL_RES_ALLOW;
+ else if ((result & ACLPB_CACHE_SEARCH_RES_DENY) ||
+ (result & ACLPB_CACHE_READ_RES_DENY) )
+ result_status = ACL_RES_DENY;
+
+ }
+ } /* end of for */
+
+ if ( result_status != ACL_RES_INVALID ) {
+ goto set_result_status;
+ }
+
+ slapi_log_error ( SLAPI_LOG_ACL, plugin_name,
+ "Recomputing the ACL Index:%d for entry:%s\n",
+ aciIndex, slapi_entry_get_ndn ( aclpb->aclpb_curr_entry) );
+
+ /* First find this one ACL and then evaluate it. */
+
+
+ aci = acllist_get_first_aci ( aclpb, &cookie );
+ while ( aci && aci->aci_index != aciIndex ) {
+ aci = acllist_get_next_aci ( aclpb, aci, &cookie );
+ }
+
+ if (NULL == aci)
+ return -1;
+
+
+ ACL_SetDefaultResult (NULL, aclpb->aclpb_acleval, ACL_RES_INVALID);
+ rv = ACL_EvalSetACL(NULL, aclpb->aclpb_acleval, aci->aci_handle);
+
+ testRight[0] = acl_access2str ( access );
+ testRight[1] = '\0';
+ aclpb->aclpb_curr_aci = aci;
+ result_status = ACL_EvalTestRights (NULL, aclpb->aclpb_acleval, testRight,
+ ds_map_generic, &unused_str1,
+ &unused_str2,
+ &acl_tag, &expr_num);
+
+ cache_result = 0;
+ if ( result_status == ACL_RES_DENY && aci->aci_type & ACI_HAS_DENY_RULE ) {
+ if ( access & SLAPI_ACL_SEARCH)
+ cache_result = ACLPB_CACHE_SEARCH_RES_DENY;
+ else
+ cache_result = ACLPB_CACHE_READ_RES_DENY;
+ } else if ( result_status == ACL_RES_ALLOW && aci->aci_type & ACI_HAS_ALLOW_RULE ) {
+ if ( access & SLAPI_ACL_SEARCH)
+ cache_result = ACLPB_CACHE_SEARCH_RES_ALLOW;
+ else
+ cache_result = ACLPB_CACHE_READ_RES_ALLOW;
+
+ } else {
+ result_status = -1;
+ }
+
+ /* Now we need to put the cached result in the aclpb */
+
+ for (j=0; j <aclpb->aclpb_last_cache_result; ++j) {
+ if (aciIndex == aclpb->aclpb_cache_result[j].aci_index) {
+ break;
+ }
+ }
+
+ if ( j < aclpb->aclpb_last_cache_result) {
+ /* already in cache */
+ } else if ( j < ACLPB_MAX_CACHE_RESULTS-1) {
+ /* rbyrneXXX: make this same as other last_cache_result code! */
+ j = ++aclpb->aclpb_last_cache_result;
+ aclpb->aclpb_cache_result[j].aci_index = aci->aci_index;
+ aclpb->aclpb_cache_result[j].aci_ruleType = aci->aci_ruleType;
+
+ } else { /* No more space */
+ goto set_result_status;
+ }
+
+ /* Add the cached result status */
+ aclpb->aclpb_cache_result[j].result |= cache_result;
+
+
+
+set_result_status:
+ if (result_status == ACL_RES_ALLOW) {
+ if (access & SLAPI_ACL_SEARCH)
+ /*wrong bit maskes were being used here--
+ a_eval->attrEval_s_status = ACLPB_CACHE_SEARCH_RES_ALLOW;*/
+ a_eval->attrEval_s_status = ACL_ATTREVAL_SUCCESS;
+ else
+ a_eval->attrEval_r_status = ACL_ATTREVAL_SUCCESS;
+
+ } else if ( result_status == ACL_RES_DENY) {
+ if (access & SLAPI_ACL_SEARCH)
+ a_eval->attrEval_s_status = ACL_ATTREVAL_FAIL;
+ else
+ a_eval->attrEval_r_status = ACL_ATTREVAL_FAIL;
+ } else {
+ /* Here, set it to recompute--try again later */
+ if (access & SLAPI_ACL_SEARCH)
+ a_eval->attrEval_s_status = ACL_ATTREVAL_RECOMPUTE;
+ else
+ a_eval->attrEval_r_status = ACL_ATTREVAL_RECOMPUTE;
+ result_status = -1;
+ }
+
+ return result_status;
+}
+
+static void
+__acl_set_aclIndex_inResult ( Acl_PBlock *aclpb, int access, int index )
+{
+ AclAttrEval *c_attrEval = aclpb->aclpb_curr_attrEval;
+
+ if ( c_attrEval ) {
+ if ( access & SLAPI_ACL_SEARCH )
+ c_attrEval->attrEval_s_aciIndex = index;
+ else if ( access & SLAPI_ACL_READ )
+ c_attrEval->attrEval_r_aciIndex = index;
+ }
+}
+
+/*
+ * If filter_sense is true then return (entry satisfies f).
+ * Otherwise, return !(entry satisfies f)
+*/
+
+static int
+acl__test_filter ( Slapi_Entry *entry, struct slapi_filter *f, int filter_sense) {
+ int filter_matched;
+
+ /* slapi_vattr_filter_test() returns 0 for match */
+
+ filter_matched = !slapi_vattr_filter_test(NULL, entry,
+ f,
+ 0 /*don't do acess chk*/);
+
+ if (filter_sense) {
+ return(filter_matched ? ACL_TRUE : ACL_FALSE);
+ } else {
+ return(filter_matched ? ACL_FALSE: ACL_TRUE);
+ }
+}
+
+/*
+ * Make an entry consisting of attr_type and attr_val and put
+ * a pointer to it in *entry.
+ * We will use this afterwards to test for against a filter.
+*/
+
+static int
+acl__make_filter_test_entry ( Slapi_Entry **entry, char *attr_type,
+ struct berval *attr_val) {
+
+ struct berval *vals_array[2];
+
+ vals_array[0] = attr_val;
+ vals_array[1] = NULL;
+
+ *entry = slapi_entry_alloc();
+ slapi_entry_init(*entry, NULL, NULL);
+
+ return (slapi_entry_add_values( *entry, (const char *)attr_type,
+ (struct berval**)&vals_array[0] ));
+
+}
+
+/*********************************************************************************/
+/* E N D */
+/*********************************************************************************/
diff --git a/ldap/servers/plugins/acl/acl.h b/ldap/servers/plugins/acl/acl.h
new file mode 100644
index 00000000..5e16a0bd
--- /dev/null
+++ b/ldap/servers/plugins/acl/acl.h
@@ -0,0 +1,867 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/*****************************************************************************
+* acl.h
+*
+* Header file for ACL processing
+*
+*****************************************************************************/
+#ifndef _ACL_H_
+#define _ACL_H_
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#ifndef _WIN32
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#endif
+
+#include <ldap.h>
+#include <las.h>
+#include <aclproto.h>
+#include <aclerror.h>
+#include "prcvar.h"
+#include "slapi-plugin.h"
+#include "slap.h"
+#include "slapi-private.h"
+#include "portable.h"
+#include "prrwlock.h"
+#include "avl.h"
+
+#include "cert.h"
+
+#include <plhash.h>
+
+#ifdef SOLARIS
+ #include <tnf/probe.h>
+#else
+ #define TNF_PROBE_0_DEBUG(a,b,c)
+ #define TNF_PROBE_1_DEBUG(a,b,c,d,e,f)
+#endif
+
+#define ACL_PLUGIN_NAME "NSACLPlugin"
+extern char *plugin_name;
+
+/*
+ * Define the OID for version 2 of the proxied authorization control if
+ * it is not already defined (it is in recent copies of ldap.h).
+ */
+#ifndef LDAP_CONTROL_PROXIEDAUTH
+#define LDAP_CONTROL_PROXIEDAUTH "2.16.840.1.113730.3.4.18"
+#endif
+
+#define ACLUCHP unsigned char *
+
+static char* const aci_attr_type = "aci";
+static char* const filter_string = "aci=*";
+static char* const aci_targetdn = "target";
+static char* const aci_targetattr = "targetattr";
+static char* const aci_targetattrfilters = "targattrfilters";
+static char* const aci_targetfilter = "targetfilter";
+
+static char* const LDAP_URL_prefix = "ldap:///";
+
+static char* const access_str_compare = "compare";
+static char* const access_str_search = "search";
+static char* const access_str_read = "read";
+static char* const access_str_write = "write";
+static char* const access_str_delete = "delete";
+static char* const access_str_add = "add";
+static char* const access_str_selfwrite = "selfwrite";
+static char* const access_str_proxy = "proxy";
+
+#define ACL_INIT_ATTR_ARRAY 5
+
+/* define the method */
+#define DS_METHOD "ds_method"
+
+#define ACL_ESCAPE_STRING_WITH_PUNCTUATION(x,y) (slapi_is_loglevel_set(SLAPI_LOG_ACL) ? escape_string_with_punctuation(x,y) : "")
+
+/* Lases */
+#define DS_LAS_USER "user"
+#define DS_LAS_GROUP "group"
+#define DS_LAS_USERDN "userdn"
+#define DS_LAS_GROUPDN "groupdn"
+#define DS_LAS_USERDNATTR "userdnattr"
+#define DS_LAS_AUTHMETHOD "authmethod"
+#define DS_LAS_GROUPDNATTR "groupdnattr"
+#define DS_LAS_USERATTR "userattr"
+#define DS_LAS_ROLEDN "roledn"
+#define DS_LAS_ROLEDNATTR "rolednattr"
+
+
+/* These define the things that aclutil_evaluate_macro() supports */
+typedef enum
+{
+ ACL_EVAL_USER,
+ ACL_EVAL_GROUP,
+ ACL_EVAL_ROLE,
+ ACL_EVAL_GROUPDNATTR,
+ ACL_EVAL_TARGET_FILTER
+}acl_eval_types;
+
+typedef enum
+{
+ ACL_RULE_MACRO_DN_TYPE,
+ ACL_RULE_MACRO_DN_LEVELS_TYPE
+}acl_rule_macro_types;
+
+#define ACL_TARGET_MACRO_DN_KEY "($dn)"
+#define ACL_RULE_MACRO_DN_KEY "($dn)"
+#define ACL_RULE_MACRO_DN_LEVELS_KEY "[$dn]"
+#define ACL_RULE_MACRO_ATTR_KEY "($attr."
+
+#define ACL_EVAL_USER 0
+#define ACL_EVAL_GROUP 1
+#define ACL_EVAL_ROLE 2
+
+/* The LASes are implemented in the libaccess library */
+#define DS_LAS_TIMEOFDAY "timeofday"
+#define DS_LAS_DAYOFWEEK "dayofweek"
+
+
+/* ACL function return codes */
+#define ACL_TRUE 1 /* evaluation results to TRUE */
+#define ACL_OK ACL_TRUE
+#define ACL_FALSE 0 /* evaluation results to FALSE */
+#define ACL_ERR -1 /* generic error */
+#define ACL_TARGET_FILTER_ERR -2 /* Target filter not set properly */
+#define ACL_TARGETATTR_FILTER_ERR -3 /* TargetAttr filter not set properly */
+#define ACL_TARGETFILTER_ERR -4 /* Target filter not set properly */
+#define ACL_SYNTAX_ERR -5 /* Syntax error */
+#define ACL_ONEACL_TEXT_ERR -6 /* ONE ACL text error */
+#define ACL_ERR_CONCAT_HANDLES -7 /* unable to concat the handles */
+#define ACL_INVALID_TARGET -8 /* invalid target */
+#define ACL_INVALID_AUTHMETHOD -9 /* multiple client auth */
+#define ACL_INVALID_AUTHORIZATION -10 /* no authorization */
+#define ACL_INCORRECT_ACI_VERSION -11 /* incorrect version # */
+#define ACL_DONT_KNOW -12 /* the world is an uncertain place */
+
+/* supported by the DS */
+#define DS_PROP_CONNECTION "connection"
+#define DS_ATTR_USERDN "userdn"
+#define DS_ATTR_ENTRY "entry"
+#define DS_PROP_ACLPB "aclblock"
+#define DS_ATTR_AUTHTYPE "authtype"
+#define DS_ATTR_CERT "clientcert"
+
+#define ACL_ANOM_MAX_ACL 40
+struct scoped_entry_anominfo {
+ short anom_e_targetInfo[ACL_ANOM_MAX_ACL];
+ short anom_e_nummatched;
+ short anom_e_isrootds;
+};
+
+typedef struct targetattr {
+ int attr_type;
+#define ACL_ATTR_FILTER 0x01
+#define ACL_ATTR_STRING 0x02
+#define ACL_ATTR_STAR 0x04 /* attr is * only */
+
+ union {
+ char *attr_str;
+ struct slapi_filter *attr_filter;
+ }u;
+}Targetattr;
+
+typedef struct targetattrfilter {
+ char *attr_str;
+ char *filterStr;
+ struct slapi_filter *filter; /* value filter */
+
+}Targetattrfilter;
+
+typedef struct Aci_Macro {
+ char *match_this;
+ char *macro_ptr; /* ptr into match_this */
+}aciMacro;
+
+typedef PLHashTable acl_ht_t;
+
+/* Access Control Item (aci): Stores information about a particular ACL */
+typedef struct aci {
+ int aci_type; /* Type of resurce */
+
+/* THE FIRST BYTE WAS USED TO KEEP THE RIGHTS. ITS BEEN MOVED TO
+** aci_access and is now free.
+**
+**
+**
+*/
+
+#define ACI_TARGET_MACRO_DN (int)0x000001
+#define ACI_TARGET_FILTER_MACRO_DN (int)0x000002
+#define ACI_TARGET_DN (int)0x000100 /* target has DN */
+#define ACI_TARGET_ATTR (int)0x000200 /* target is an attr */
+#define ACI_TARGET_PATTERN (int)0x000400 /* target has some patt */
+#define ACI_TARGET_FILTER (int)0x000800 /* target has a filter */
+#define ACI_ACLTXT (int)0x001000 /* ACI has text only */
+#define ACI_TARGET_NOT (int)0x002000 /* it's a != */
+#define ACI_TARGET_ATTR_NOT (int)0x004000 /* It's a != manager */
+#define ACI_TARGET_FILTER_NOT (int)0x008000 /* It's a != filter */
+#define ACI_UNUSED2 (int)0x010000 /* Unused */
+#define ACI_HAS_ALLOW_RULE (int)0x020000 /* allow (...) */
+#define ACI_HAS_DENY_RULE (int)0x040000 /* deny (...) */
+#define ACI_CONTAIN_NOT_USERDN (int)0x080000 /* userdn != blah */
+#define ACI_TARGET_ATTR_ADD_FILTERS (int)0x100000
+#define ACI_TARGET_ATTR_DEL_FILTERS (int)0x200000
+#define ACI_CONTAIN_NOT_GROUPDN (int)0x400000 /* groupdn != blah */
+#define ACI_CONTAIN_NOT_ROLEDN (int)0x800000
+
+ int aci_access;
+
+/*
+ * See also aclpb_access which is used to store rights too.
+*/
+
+ short aci_ruleType; /* kinds of rules in the ACL */
+
+#define ACI_USERDN_RULE (short) 0x0001
+#define ACI_USERDNATTR_RULE (short) 0x0002
+#define ACI_GROUPDN_RULE (short) 0x0004
+#define ACI_GROUPDNATTR_RULE (short) 0x0008
+#define ACI_AUTHMETHOD_RULE (short) 0x0010
+#define ACI_IP_RULE (short) 0x0020
+#define ACI_DNS_RULE (short) 0x0040
+#define ACI_TIMEOFDAY_RULE (short) 0x0080
+#define ACI_DAYOFWEEK_RULE (short) 0x0010
+#define ACI_USERATTR_RULE (short) 0x0200
+/*
+ * These are extension of USERDN/GROUPDN rule. However since the
+ * semantics are quite different, we classify them as different rules.
+ * ex: groupdn = "ldap:///cn=helpdesk, ou=$attr.dept, o=$dn.o, o=isp"
+ */
+#define ACI_PARAM_DNRULE (short) 0x0400
+#define ACI_PARAM_ATTRRULE (short) 0x0800
+#define ACI_USERDN_SELFRULE (short) 0x1000
+#define ACI_ROLEDN_RULE (short) 0x2000
+
+
+
+#define ACI_ATTR_RULES ( ACI_USERDNATTR_RULE | ACI_GROUPDNATTR_RULE | ACI_USERATTR_RULE | ACI_PARAM_DNRULE | ACI_PARAM_ATTRRULE | ACI_USERDN_SELFRULE)
+#define ACI_CACHE_RESULT_PER_ENTRY ACI_ATTR_RULES
+
+ short aci_elevel; /* Based on the aci type some idea about the
+ ** execution flow
+ */
+ int aci_index; /* index # */
+ Slapi_DN *aci_sdn; /* location */
+ Slapi_Filter *target; /* Target is a DN */
+ Targetattr **targetAttr;
+ char *targetFilterStr;
+ struct slapi_filter *targetFilter; /* Target has a filter */
+ Targetattrfilter **targetAttrAddFilters;
+ Targetattrfilter **targetAttrDelFilters;
+ char *aclName; /* ACL name */
+ struct ACLListHandle *aci_handle; /*handle of the ACL */
+ aciMacro *aci_macro;
+ struct aci *aci_next; /* next one */
+}aci_t;
+
+/* Aci excution level
+** The idea is that for each handle types, we can prioritize which one to evaluate first.
+** Evaluating the user before the group is better.
+*/
+#define ACI_ELEVEL_USERDN_ANYONE 0
+#define ACI_ELEVEL_USERDN_ALL 1
+#define ACI_ELEVEL_USERDN 2
+#define ACI_ELEVEL_USERDNATTR 3
+#define ACI_ELEVEL_GROUPDNATTR_URL 4
+#define ACI_ELEVEL_GROUPDNATTR 5
+#define ACI_ELEVEL_GROUPDN 6
+#define ACI_MAX_ELEVEL ACI_ELEVEL_GROUPDN +1
+#define ACI_DEFAULT_ELEVEL ACI_MAX_ELEVEL
+
+
+#define ACLPB_MAX_SELECTED_ACLS 200
+
+typedef struct result_cache {
+ int aci_index;
+ short aci_ruleType;
+ short result;
+#define ACLPB_CACHE_READ_RES_ALLOW (short)0x0001 /* used for ALLOW handles only */
+#define ACLPB_CACHE_READ_RES_DENY (short)0x0002 /* used for DENY handles only */
+#define ACLPB_CACHE_SEARCH_RES_ALLOW (short)0x0004 /* used for ALLOW handles only */
+#define ACLPB_CACHE_SEARCH_RES_DENY (short)0x0008 /* used for DENY handles only */
+#define ACLPB_CACHE_SEARCH_RES_SKIP (short)0x0010 /* used for both types */
+#define ACLPB_CACHE_READ_RES_SKIP (short)0x0020 /* used for both types */
+}r_cache_t;
+#define ACLPB_MAX_CACHE_RESULTS ACLPB_MAX_SELECTED_ACLS
+
+/*
+ * This is use to keep the result of the evaluation of the attr.
+ * We are only intrested in read/searc only.
+ */
+struct acl_attrEval {
+ char *attrEval_name; /* Attribute Name */
+ short attrEval_r_status; /* status of read evaluation */
+ short attrEval_s_status; /* status of search evaluation */
+ int attrEval_r_aciIndex; /* Index of the ACL which grants access*/
+ int attrEval_s_aciIndex; /* Index of the ACL which grants access*/
+
+#define ACL_ATTREVAL_SUCCESS 0x1
+#define ACL_ATTREVAL_FAIL 0x2
+#define ACL_ATTREVAL_RECOMPUTE 0x4
+#define ACL_ATTREVAL_DETERMINISTIC 7
+#define ACL_ATTREVAL_INVALID 0x8
+
+};
+typedef struct acl_attrEval AclAttrEval;
+
+
+/*
+ * Struct to keep the evaluation context information. This struct is
+ * used in multiple places ( different instance ) to keep the context for
+ * current entry evaluation, previous entry evaluation or previous operation
+ * evaluation status.
+ */
+#define ACLPB_MAX_ATTR_LEN 100
+#define ACLPB_MAX_ATTRS 100
+struct acleval_context {
+
+ /* Information about the attrs */
+ AclAttrEval acle_attrEval[ACLPB_MAX_ATTRS];
+ short acle_numof_attrs;
+
+ /* Handles information */
+ short acle_numof_tmatched_handles;
+ int acle_handles_matched_target[ACLPB_MAX_SELECTED_ACLS];
+};
+typedef struct acleval_context aclEvalContext;
+
+
+struct acl_usergroup {
+ short aclug_signature;
+/*
+ * To modify refcnt you need either the write lock on the whole cache or
+ * the reader lock on the whole cache plus this refcnt mutex
+*/
+ short aclug_refcnt;
+ PRLock *aclug_refcnt_mutex;
+
+
+ char *aclug_ndn; /* Client's normalized DN */
+
+ char **aclug_member_groups;
+ short aclug_member_group_size;
+ short aclug_numof_member_group;
+
+ char **aclug_notmember_groups;
+ short aclug_notmember_group_size;
+ short aclug_numof_notmember_group;
+ struct acl_usergroup *aclug_next;
+ struct acl_usergroup *aclug_prev;
+
+};
+typedef struct acl_usergroup aclUserGroup;
+
+#define ACLUG_INCR_GROUPS_LIST 20
+
+struct aci_container {
+ Slapi_DN *acic_sdn; /* node DN */
+ aci_t *acic_list; /* List of the ACLs for that node */
+ int acic_index; /* index to the container array */
+};
+typedef struct aci_container AciContainer;
+
+struct acl_pblock {
+ int aclpb_state;
+
+#define ACLPB_ACCESS_ALLOWED_ON_A_ATTR 0x000001
+#define ACLPB_ACCESS_DENIED_ON_ALL_ATTRS 0x000002
+#define ACLPB_ACCESS_ALLOWED_ON_ENTRY 0x000004
+#define ACLPB_ATTR_STAR_MATCHED 0x000008
+#define ACLPB_FOUND_ATTR_RULE 0x000010
+#define ACLPB_SEARCH_BASED_ON_LIST 0x000020
+#define ACLPB_EXECUTING_DENY_HANDLES 0x000040
+#define ACLPB_EXECUTING_ALLOW_HANDLES 0x000080
+#define ACLPB_ACCESS_ALLOWED_USERATTR 0x000100
+#ifdef DETERMINE_ACCESS_BASED_ON_REQUESTED_ATTRIBUTES
+ #define ACLPB_USER_SPECIFIED_ATTARS 0x000200
+ #define ACLPB_USER_WANTS_ALL_ATTRS 0x000400
+#endif
+#define ACLPB_EVALUATING_FIRST_ATTR 0x000800
+#define ACLPB_FOUND_A_ENTRY_TEST_RULE 0x001000
+#define ACLPB_SEARCH_BASED_ON_ENTRY_LIST 0x002000
+#define ACLPB_DONOT_USE_CONTEXT_ACLS 0x004000
+#define ACLPB_HAS_ACLCB_EVALCONTEXT 0x008000
+#define ACLPB_COPY_EVALCONTEXT 0x010000
+#define ACLPB_MATCHES_ALL_ACLS 0x020000
+#define ACLPB_INITIALIZED 0x040000
+#define ACLPB_INCR_ACLCB_CACHE 0x080000
+#define ACLPB_UPD_ACLCB_CACHE 0x100000
+#define ACLPB_ATTR_RULE_EVALUATED 0x200000
+#define ACLPB_DONOT_EVALUATE_PROXY 0x400000
+
+
+#define ACLPB_RESET_MASK ( ACLPB_ACCESS_ALLOWED_ON_A_ATTR | ACLPB_ACCESS_DENIED_ON_ALL_ATTRS | \
+ ACLPB_ACCESS_ALLOWED_ON_ENTRY | ACLPB_ATTR_STAR_MATCHED | \
+ ACLPB_FOUND_ATTR_RULE | ACLPB_EVALUATING_FIRST_ATTR | \
+ ACLPB_FOUND_A_ENTRY_TEST_RULE )
+#define ACLPB_STATE_ALL 0x3fffff
+
+ int aclpb_res_type;
+
+ #define ACLPB_NEW_ENTRY 0x100
+ #define ACLPB_EFFECTIVE_RIGHTS 0x200
+ #define ACLPB_RESTYPE_ALL 0x7ff
+
+ /*
+ * The bottom bye used to be for rights. It's free now as they have
+ * been moved to aclpb_access.
+ */
+
+ int aclpb_access;
+
+#define ACLPB_SLAPI_ACL_WRITE_ADD 0x200
+#define ACLPB_SLAPI_ACL_WRITE_DEL 0x400
+
+ /* stores the requested access during an operation */
+
+ short aclpb_signature;
+ short aclpb_type;
+#define ACLPB_TYPE_MAIN 1
+#define ACLPB_TYPE_MAIN_STR "Main Block"
+#define ACLPB_TYPE_PROXY 2
+#define ACLPB_TYPE_PROXY_STR "Proxy Block"
+
+ Slapi_Entry *aclpb_client_entry; /* A copy of client's entry */
+ Slapi_PBlock *aclpb_pblock; /* back to LDAP PBlock */
+ int aclpb_optype; /* current optype from pb */
+
+ /* Current entry/dn/attr evaluation info */
+ Slapi_Entry *aclpb_curr_entry; /* current Entry being processed */
+ int aclpb_num_entries;
+ Slapi_DN *aclpb_curr_entry_sdn; /* Entry's SDN */
+ Slapi_DN *aclpb_authorization_sdn; /* dn used for authorization */
+
+ AclAttrEval *aclpb_curr_attrEval; /* Current attr being evaluated */
+ struct berval *aclpb_curr_attrVal; /* Value of Current attr */
+ Slapi_Entry *aclpb_filter_test_entry; /* Scratch entry */
+ aci_t *aclpb_curr_aci;
+ char *aclpb_Evalattr; /* The last attr evaluated */
+
+ /* Plist and eval info */
+ ACLEvalHandle_t *aclpb_acleval; /* acleval handle for evaluation */
+ struct PListStruct_s *aclpb_proplist;/* All the needed property */
+
+ /* DENY ACI HANDLES */
+ aci_t **aclpb_deny_handles;
+ int aclpb_deny_handles_size;
+ int aclpb_num_deny_handles;
+
+ /* ALLOW ACI HANDLES */
+ aci_t **aclpb_allow_handles;
+ int aclpb_allow_handles_size;
+ int aclpb_num_allow_handles;
+
+ /* This is used in the groupdnattr="URL" rule
+ ** Keep a list of base where searched has been done
+ */
+ char **aclpb_grpsearchbase;
+ int aclpb_grpsearchbase_size;
+ int aclpb_numof_bases;
+
+ aclUserGroup *aclpb_groupinfo;
+
+ /* Keep the Group nesting level */
+ int aclpb_max_nesting_level;
+ int aclpb_max_member_sizelimit;
+
+
+ /* To keep the results in the cache */
+
+ int aclpb_last_cache_result;
+ struct result_cache aclpb_cache_result[ACLPB_MAX_CACHE_RESULTS];
+
+ /* Index numbers of ACLs selected based on a locality search*/
+ char *aclpb_search_base;
+ int aclpb_base_handles_index[ACLPB_MAX_SELECTED_ACLS];
+ int aclpb_handles_index[ACLPB_MAX_SELECTED_ACLS];
+
+ /* Evaluation context info
+ ** 1) Context cached from aclcb ( from connection struct )
+ ** 2) Context cached from previous entry evaluation
+ ** 3) current entry evaluation info
+ */
+ aclEvalContext aclpb_curr_entryEval_context;
+ aclEvalContext aclpb_prev_entryEval_context;
+ aclEvalContext aclpb_prev_opEval_context;
+
+ /* Currentry anom profile sumamry */
+ struct scoped_entry_anominfo aclpb_scoped_entry_anominfo;
+
+ /* Some Statistics gathering */
+ PRUint16 aclpb_stat_acllist_scanned;
+ PRUint16 aclpb_stat_aclres_matched;
+ PRUint16 aclpb_stat_total_entries;
+ PRUint16 aclpb_stat_anom_list_scanned;
+ PRUint16 aclpb_stat_num_copycontext;
+ PRUint16 aclpb_stat_num_copy_attrs;
+ PRUint16 aclpb_stat_num_tmatched_acls;
+ PRUint16 aclpb_stat_unused;
+ CERTCertificate *aclpb_clientcert;
+ AciContainer *aclpb_aclContainer;
+ struct acl_pblock *aclpb_proxy; /* Child proxy block */
+ acl_ht_t *aclpb_macro_ht; /* ht for partial macro strs */
+
+ struct acl_pblock *aclpb_prev; /* Previpous in the chain */
+ struct acl_pblock *aclpb_next; /* Next in the chain */
+};
+typedef struct acl_pblock Acl_PBlock;
+
+/* PBLCOK TYPES */
+typedef enum
+{
+ ACLPB_BINDDN_PBLOCK,
+ ACLPB_PROXYDN_PBLOCK,
+ ACLPB_ALL_PBLOCK
+}aclpb_types;
+
+
+#define ACLPB_EVALCONTEXT_CURR 1
+#define ACLPB_EVALCONTEXT_PREV 2
+#define ACLPB_EVALCONTEXT_ACLCB 3
+
+
+
+/* Cleaning/ deallocating/ ... acl_freeBlock() */
+#define ACL_CLEAN_ACLPB 1
+#define ACL_COPY_ACLCB 2
+#define ACL_CLEAN_ACLCB 3
+
+/* used to differentiate acl plugins sharing the same lib */
+#define ACL_PLUGIN_IDENTITY 1
+#define ACL_PREOP_PLUGIN_IDENTITY 2
+
+
+/* start with 50 and then add 50 more as required
+ * The first ACI_MAX_ELEVEL slots are predefined.
+ */
+#define ACLPB_INCR_LIST_HANDLES ACI_MAX_ELEVEL + 43
+
+#define ACLPB_INCR_BASES 5
+
+/*
+ * acl private block which hangs from connection structure.
+ * This is allocated the first time an operation is done and freed when the
+ * connection are cleaned.
+ *
+ */
+struct acl_cblock {
+
+ short aclcb_aclsignature;
+ short aclcb_state;
+#define ACLCB_HAS_CACHED_EVALCONTEXT 0x1
+
+ Slapi_DN *aclcb_sdn; /* Contains bind SDN */
+ aclEvalContext aclcb_eval_context;
+ PRLock *aclcb_lock; /* shared lock */
+};
+
+struct acl_groupcache {
+ short aclg_state; /* status information */
+ short aclg_signature;
+ int aclg_num_userGroups;
+ aclUserGroup *aclg_first;
+ aclUserGroup *aclg_last;
+ PRRWLock *aclg_rwlock; /* lock to monitor the group cache */
+};
+typedef struct acl_groupcache aclGroupCache;
+
+
+/* Type of extensions that can be registered */
+typedef enum
+{
+ ACL_EXT_OPERATION, /* extension for Operation object */
+ ACL_EXT_CONNECTION, /* extension for Connection object */
+ ACL_EXT_ALL
+}ext_type;
+
+/* Used to pass data around in acllas.c */
+
+typedef struct {
+ char *clientDn;
+ char *authType;
+ int anomUser;
+ Acl_PBlock *aclpb;
+ Slapi_Entry *resourceEntry;
+
+}lasInfo;
+
+
+/* reasons why the subject allowed/denied access--good for logs */
+
+typedef enum{
+ACL_REASON_NO_ALLOWS,
+ACL_REASON_RESULT_CACHED_DENY,
+ACL_REASON_EVALUATED_DENY, /* evaluated deny */
+ACL_REASON_RESULT_CACHED_ALLOW, /* cached allow */
+ACL_REASON_EVALUATED_ALLOW, /* evalauted allow */
+ACL_REASON_NO_MATCHED_RESOURCE_ALLOWS, /* were allows/denies, but none matched */
+ACL_REASON_NONE, /* no reason available */
+ACL_REASON_ANON_ALLOWED,
+ACL_REASON_ANON_DENIED,
+ACL_REASON_NO_MATCHED_SUBJECT_ALLOWS,
+ACL_REASON_EVALCONTEXT_CACHED_ALLOW,
+ACL_REASON_EVALCONTEXT_CACHED_NOT_ALLOWED,
+ACL_REASON_EVALCONTEXT_CACHED_ATTR_STAR_ALLOW
+}aclReasonCode_t;
+
+typedef struct{
+ aci_t *deciding_aci;
+ aclReasonCode_t reason;
+}aclResultReason_t;
+#define ACL_NO_DECIDING_ACI_INDEX -10
+
+
+/* Extern declaration for backend state change fnc: acllist.c and aclinit.c */
+
+void acl_be_state_change_fnc ( void *handle, char *be_name, int old_state,
+ int new_state);
+
+
+/* Extern declaration for ATTRs */
+
+extern int
+DS_LASIpGetter(NSErr_t *errp, PList_t subject, PList_t resource, PList_t
+ auth_info, PList_t global_auth, void *arg);
+extern int
+DS_LASDnsGetter(NSErr_t *errp, PList_t subject, PList_t resource, PList_t
+ auth_info, PList_t global_auth, void *arg);
+extern int
+DS_LASUserDnGetter(NSErr_t *errp, PList_t subject, PList_t resource, PList_t
+ auth_info, PList_t global_auth, void *arg);
+extern int
+DS_LASGroupDnGetter(NSErr_t *errp, PList_t subject, PList_t resource, PList_t
+ auth_info, PList_t global_auth, void *arg);
+extern int
+DS_LASEntryGetter(NSErr_t *errp, PList_t subject, PList_t resource,
+ PList_t auth_info, PList_t global_auth, void *arg);
+
+extern int
+DS_LASCertGetter(NSErr_t *errp, PList_t subject, PList_t resource,
+ PList_t auth_info, PList_t global_auth, void *arg);
+
+/* function declartion for LAses supported by DS */
+
+extern int DS_LASUserEval(NSErr_t *errp, char *attribute, CmpOp_t comparator,
+ char *pattern, int *cachable, void **las_cookie,
+ PList_t subject, PList_t resource, PList_t auth_info,
+ PList_t global_auth);
+
+extern int DS_LASGroupEval(NSErr_t *errp, char *attribute, CmpOp_t comparator,
+ char *pattern, int *cachable, void **las_cookie,
+ PList_t subject, PList_t resource, PList_t auth_info,
+ PList_t global_auth);
+
+extern int DS_LASUserDnEval(NSErr_t *errp, char *attribute, CmpOp_t comparator,
+ char *pattern, int *cachable, void **las_cookie,
+ PList_t subject, PList_t resource, PList_t auth_info,
+ PList_t global_auth);
+
+extern int DS_LASGroupDnEval(NSErr_t *errp, char *attribute, CmpOp_t comparator,
+ char *pattern, int *cachable, void **las_cookie,
+ PList_t subject, PList_t resource, PList_t auth_info,
+ PList_t global_auth);
+
+extern int DS_LASRoleDnEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator,
+ char *attr_pattern, int *cachable, void **LAS_cookie,
+ PList_t subject, PList_t resource, PList_t auth_info,
+ PList_t global_auth);
+
+extern int DS_LASUserDnAttrEval(NSErr_t *errp, char *attribute,
+ CmpOp_t comparator,
+ char *pattern, int *cachable, void **las_cookie,
+ PList_t subject, PList_t resource, PList_t auth_info,
+ PList_t global_auth);
+
+extern int DS_LASAuthMethodEval(NSErr_t *errp, char *attribute,
+ CmpOp_t comparator,
+ char *pattern, int *cachable, void **las_cookie,
+ PList_t subject, PList_t resource, PList_t auth_info,
+ PList_t global_auth);
+
+extern int DS_LASGroupDnAttrEval(NSErr_t *errp, char *attribute,
+ CmpOp_t comparator,
+ char *pattern, int *cachable, void **las_cookie,
+ PList_t subject, PList_t resource, PList_t auth_info,
+ PList_t global_auth);
+
+extern int DS_LASRoleDnAttrEval(NSErr_t *errp, char *attribute,
+ CmpOp_t comparator,
+ char *pattern, int *cachable, void **las_cookie,
+ PList_t subject, PList_t resource, PList_t auth_info,
+ PList_t global_auth);
+
+extern int DS_LASUserAttrEval(NSErr_t *errp, char *attribute,
+ CmpOp_t comparator,
+ char *pattern, int *cachable, void **las_cookie,
+ PList_t subject, PList_t resource, PList_t auth_info,
+ PList_t global_auth);
+
+/* other function declaration */
+int aclinit_main();
+int acl_match_substring (struct slapi_filter *f, char *str, int match);
+void acl_print_acllib_err(NSErr_t *errp, char * str);
+void acl_initBlock ( Slapi_PBlock *pb );
+void acl_freeBlock ( Slapi_PBlock *pb, int state );
+int acl_read_access_allowed_on_entry ( Slapi_PBlock *pb, Slapi_Entry *e,
+ char **attrs, int access);
+int acl_access_allowed_modrdn ( Slapi_PBlock *pb, Slapi_Entry *e, char *attr,
+ struct berval *val, int access);
+int acl_read_access_allowed_on_attr ( Slapi_PBlock *pb, Slapi_Entry *e, char *attr,
+ struct berval *val, int access);
+void acl_set_acllist (Slapi_PBlock *pb, int scope, char *base);
+void acl_gen_err_msg(int access, char *edn, char *attr, char **errbuf);
+void acl_modified ( Slapi_PBlock *pb, int optype, char *dn, void *change);
+int acl_access_allowed_disjoint_resource( Slapi_PBlock *pb, Slapi_Entry *e,
+ char *attr, struct berval *val, int access );
+int acl_access_allowed_main ( Slapi_PBlock *pb, Slapi_Entry *e, char **attrs,
+ struct berval *val, int access , int flags, char **errbuf);
+int acl_access_allowed( Slapi_PBlock *pb, Slapi_Entry *e, char *attr,
+ struct berval *val, int access );
+int acl_verify_syntax(const Slapi_DN *e_sdn, const struct berval *bval);
+aclUserGroup * acl_get_usersGroup ( struct acl_pblock *aclpb , char *n_dn);
+void acl_print_acllib_err (NSErr_t *errp , char * str);
+int acl_check_mods( Slapi_PBlock *pb, Slapi_Entry *e, LDAPMod **mods, char **errbuf );
+int acl_verify_aci_syntax (Slapi_Entry *e, char **errbuf);
+char * acl__access2str(int access);
+void acl_strcpy_special (char *d, char *s);
+int acl_parse(char * str, aci_t *aci_item);
+char * acl_access2str ( int access );
+int acl_init_ext ();
+void * acl_get_ext (ext_type type, void *object);
+void acl_set_ext (ext_type type, void *object, void *data);
+void acl_reset_ext_status (ext_type type, void *object);
+void acl_init_op_ext ( Slapi_PBlock *pb , int type, char *dn, int copy);
+void * acl_operation_ext_constructor (void *object, void *parent );
+void acl_operation_ext_destructor ( void *ext, void *object, void *parent );
+void * acl_conn_ext_constructor (void *object, void *parent );
+void acl_conn_ext_destructor ( void *ext, void *object, void *parent );
+void acl_clean_aclEval_context ( aclEvalContext *clean_me, int scrub_only );
+void acl_copyEval_context ( struct acl_pblock *aclpb, aclEvalContext *src,
+ aclEvalContext *dest , int copy_attr_only );
+struct acl_pblock * acl_get_aclpb ( Slapi_PBlock *pb, int type );
+int acl_client_anonymous ( Slapi_PBlock *pb );
+short acl_get_aclsignature();
+void acl_set_aclsignature( short value);
+void acl_regen_aclsignature();
+struct acl_pblock * acl_new_proxy_aclpb( Slapi_PBlock *pb );
+void acl_set_authorization_dn( Slapi_PBlock *pb, char *dn, int type );
+int acl_get_proxyauth_dn( Slapi_PBlock *pb, char **proxydnp,
+ char **errtextp );
+void acl_init_aclpb ( Slapi_PBlock *pb , Acl_PBlock *aclpb,
+ const char *dn, int copy_from_aclcb);
+int acl_create_aclpb_pool ();
+int acl_skip_access_check ( Slapi_PBlock *pb, Slapi_Entry *e );
+
+int aclext_alloc_lockarray ();
+
+int aclutil_str_appened(char **str1, const char *str2);
+void aclutil_print_err (int rv , const Slapi_DN *sdn,
+ const struct berval* val, char **errbuf);
+void aclutil_print_aci (aci_t *aci_item, char *type);
+short aclutil_gen_signature ( short c_signature );
+void aclutil_print_resource( struct acl_pblock *aclpb, char *right , char *attr, char *clientdn );
+char * aclutil_expand_paramString ( char *str, Slapi_Entry *e );
+
+
+void acllist_init_scan (Slapi_PBlock *pb, int scope, char *base);
+aci_t * acllist_get_first_aci (Acl_PBlock *aclpb, PRUint32 *cookie );
+aci_t * acllist_get_next_aci ( Acl_PBlock *aclpb, aci_t *curraci, PRUint32 *cookie );
+aci_t * acllist_get_aci_new ();
+void acllist_free_aci (aci_t *item);
+void acllist_acicache_READ_UNLOCK(void);
+void acllist_acicache_READ_LOCK(void);
+void acllist_acicache_WRITE_UNLOCK(void);
+void acllist_acicache_WRITE_LOCK(void);
+void acllist_aciscan_update_scan ( Acl_PBlock *aclpb, char *edn );
+int acllist_remove_aci_needsLock( const Slapi_DN *sdn, const struct berval *attr );
+int acllist_insert_aci_needsLock( const Slapi_DN *e_sdn, const struct berval* aci_attr);
+int acllist_init ();
+int acllist_moddn_aci_needsLock ( Slapi_DN *oldsdn, char *newdn );
+void acllist_print_tree ( Avlnode *root, int *depth, char *start, char *side);
+AciContainer *acllist_get_aciContainer_new ( );
+void acllist_done_aciContainer ( AciContainer *);
+
+aclUserGroup* aclg_find_userGroup (char *n_dn);
+void aclg_regen_ugroup_signature( aclUserGroup *ugroup);
+void aclg_markUgroupForRemoval ( aclUserGroup *u_group );
+void aclg_reader_incr_ugroup_refcnt(aclUserGroup* u_group);
+int aclg_numof_usergroups(void);
+int aclgroup_init ();
+void aclg_regen_group_signature ();
+void aclg_reset_userGroup ( struct acl_pblock *aclpb );
+void aclg_init_userGroup ( struct acl_pblock *aclpb, const char *dn , int got_lock);
+aclUserGroup * aclg_get_usersGroup ( struct acl_pblock *aclpb , char *n_dn);
+
+void aclg_lock_groupCache (int type );
+void aclg_unlock_groupCache (int type );
+
+int aclanom_init();
+int aclanom_match_profile (Slapi_PBlock *pb, struct acl_pblock *aclpb,
+ Slapi_Entry *e, char *attr, int access);
+void aclanom_get_suffix_info(Slapi_Entry *e, struct acl_pblock *aclpb );
+void aclanom_invalidateProfile();
+typedef enum{
+ DONT_TAKE_ACLCACHE_READLOCK,
+ DO_TAKE_ACLCACHE_READLOCK,
+ DONT_TAKE_ACLCACHE_WRITELOCK,
+ DO_TAKE_ACLCACHE_WRITELOCK
+}acl_lock_flag_t;
+void aclanom_gen_anomProfile (acl_lock_flag_t lock_flag);
+int aclanom_is_client_anonymous ( Slapi_PBlock *pb );
+int aclinit_main ();
+typedef struct aclinit_handler_callback_data {
+#define ACL_ADD_ACIS 1
+#define ACL_REMOVE_ACIS 0
+ int op;
+ int retCode;
+ acl_lock_flag_t lock_flag;
+}aclinit_handler_callback_data_t;
+int
+aclinit_search_and_update_aci ( int thisbeonly, const Slapi_DN *base,
+ char *be_name, int scope, int op,
+ acl_lock_flag_t lock_flag);
+void *aclplugin_get_identity(int plug);
+int
+acl_dn_component_match( const char *ndn, char *match_this, int component_number);
+char *
+acl_match_macro_in_target( const char *ndn, char *match_this,
+ char *macro_ptr);
+char* get_next_component(char *dn, int *index);
+int acl_match_prefix( char *macro_prefix, const char *ndn,
+ int *exact_match);
+char *
+get_this_component(char *dn, int *index);
+int
+acl_find_comp_end( char * s);
+char *
+acl_replace_str(char * s, char *substr, char* replace_with);
+int acl_strstr(char * s, char *substr);
+int aclutil_evaluate_macro( char * rule, lasInfo *lasinfo,
+ acl_eval_types evalType );
+
+/* acl hash table functions */
+void acl_ht_add_and_freeOld(acl_ht_t * acl_ht, PLHashNumber key,char *value);
+acl_ht_t *acl_ht_new(void);
+void acl_ht_free_all_entries_and_values( acl_ht_t *acl_ht);
+void acl_ht_remove( acl_ht_t *acl_ht, PLHashNumber key);
+void *acl_ht_lookup( acl_ht_t *acl_ht, PLHashNumber key);
+void acl_ht_display_ht( acl_ht_t *acl_ht);
+
+/* acl get effective rights */
+int
+acl_get_effective_rights ( Slapi_PBlock *pb, Slapi_Entry *e,
+ char **attrs, struct berval *val, int access, char **errbuf );
+
+char* aclutil__access_str (int type , char str[]);
+
+#endif /* _ACL_H_ */
diff --git a/ldap/servers/plugins/acl/acl_ext.c b/ldap/servers/plugins/acl/acl_ext.c
new file mode 100644
index 00000000..28a9ce18
--- /dev/null
+++ b/ldap/servers/plugins/acl/acl_ext.c
@@ -0,0 +1,968 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include "acl.h"
+
+static void acl__done_aclpb ( struct acl_pblock *aclpb );
+static void acl__dump_stats ( struct acl_pblock *aclpb , const char *block_type);
+static Acl_PBlock * acl__get_aclpb_from_pool ( );
+static int acl__put_aclpb_back_to_pool ( Acl_PBlock *aclpb );
+static Acl_PBlock * acl__malloc_aclpb ( );
+static char * acl__get_aclpb_type ( Acl_PBlock *aclpb );
+static PRLock *aclext_get_lock ();
+
+
+struct acl_pbqueue {
+ Acl_PBlock *aclq_free;
+ Acl_PBlock *aclq_busy;
+ short aclq_nfree;
+ short aclq_nbusy;
+ PRLock *aclq_lock;
+};
+typedef struct acl_pbqueue Acl_PBqueue;
+
+static Acl_PBqueue *aclQueue;
+
+/* structure with information for each extension */
+typedef struct acl_ext
+{
+ char *object_name; /* name of the object extended */
+ int object_type; /* handle to the extended object */
+ int handle; /* extension handle */
+} acl_ext;
+
+static acl_ext acl_ext_list [ACL_EXT_ALL];
+
+/*
+ * EXTENSION INITIALIZATION, CONSTRUCTION, & DESTRUCTION
+ *
+ */
+int
+acl_init_ext ()
+{
+ int rc;
+
+ acl_ext_list[ACL_EXT_OPERATION].object_name = SLAPI_EXT_OPERATION;
+
+ rc = slapi_register_object_extension(plugin_name, SLAPI_EXT_OPERATION,
+ acl_operation_ext_constructor,
+ acl_operation_ext_destructor,
+ &acl_ext_list[ACL_EXT_OPERATION].object_type,
+ &acl_ext_list[ACL_EXT_OPERATION].handle);
+
+ if ( rc != 0 ) return rc;
+
+ acl_ext_list[ACL_EXT_CONNECTION].object_name = SLAPI_EXT_CONNECTION;
+ rc = slapi_register_object_extension(plugin_name, SLAPI_EXT_CONNECTION,
+ acl_conn_ext_constructor,
+ acl_conn_ext_destructor,
+ &acl_ext_list[ACL_EXT_CONNECTION].object_type,
+ &acl_ext_list[ACL_EXT_CONNECTION].handle);
+
+ return rc;
+
+}
+
+/* Interface to get the extensions */
+void *
+acl_get_ext (ext_type type, void *object)
+{
+ struct acl_ext ext;
+ void *data;
+
+ if ( type >= ACL_EXT_ALL ) {
+ slapi_log_error ( SLAPI_LOG_ACL, plugin_name,
+ "Invalid extension type:%d\n", type );
+ return NULL;
+ }
+
+ /* find the requested extension */
+ ext = acl_ext_list [type];
+ data = slapi_get_object_extension(ext.object_type, object, ext.handle);
+
+ return data;
+}
+
+void
+acl_set_ext (ext_type type, void *object, void *data)
+{
+ if ( type >= 0 && type < ACL_EXT_ALL )
+ {
+ struct acl_ext ext = acl_ext_list [type];
+ slapi_set_object_extension ( ext.object_type, object, ext.handle, data );
+ }
+}
+
+/****************************************************************************
+ * Global lock array so that private extension between connection and operation
+ * co-exist
+ *
+ ******************************************************************************/
+struct ext_lockArray {
+ PRLock **lockArray;
+ int numlocks;
+};
+
+static struct ext_lockArray extLockArray;
+
+/* PKBxxx: make this a configurable. Start with 2 * maxThreads */
+#define ACLEXT_MAX_LOCKS 40
+
+int
+aclext_alloc_lockarray ( )
+{
+
+ int i;
+ PRLock *lock;
+
+ extLockArray.lockArray =
+ (PRLock **) slapi_ch_calloc ( ACLEXT_MAX_LOCKS, sizeof ( PRLock *) );
+
+ for ( i =0; i < ACLEXT_MAX_LOCKS; i++) {
+ if (NULL == (lock = PR_NewLock()) ) {
+ slapi_log_error( SLAPI_LOG_FATAL, plugin_name,
+ "Unable to allocate locks used for private extension\n");
+ return 1;
+ }
+ extLockArray.lockArray[i] = lock;
+ }
+ extLockArray.numlocks = ACLEXT_MAX_LOCKS;
+ return 0;
+}
+static PRUint32 slot_id =0;
+static PRLock *
+aclext_get_lock ()
+{
+
+ PRUint16 slot = slot_id % ACLEXT_MAX_LOCKS;
+ slot_id++;
+ return ( extLockArray.lockArray[slot] );
+
+}
+/****************************************************************************/
+/* CONNECTION EXTENSION SPECIFIC */
+/****************************************************************************/
+void *
+acl_conn_ext_constructor ( void *object, void *parent )
+{
+ struct acl_cblock *ext = NULL;
+
+ ext = (struct acl_cblock * ) slapi_ch_calloc (1, sizeof (struct acl_cblock ) );
+ if (( ext->aclcb_lock = aclext_get_lock () ) == NULL ) {
+ slapi_log_error( SLAPI_LOG_FATAL, plugin_name,
+ "Unable to get Read/Write lock for CONNECTION extension\n");
+ slapi_ch_free ( (void **) &ext );
+ return NULL;
+ }
+ ext->aclcb_sdn = slapi_sdn_new ();
+ /* store the signatures */
+ ext->aclcb_aclsignature = acl_get_aclsignature();
+ ext->aclcb_state = -1;
+ return ext;
+
+
+}
+
+void
+acl_conn_ext_destructor ( void *ext, void *object, void *parent )
+{
+ struct acl_cblock *aclcb = ext;
+ PRLock *shared_lock;
+
+ if ( NULL == aclcb ) return;
+ PR_Lock ( aclcb->aclcb_lock );
+ shared_lock = aclcb->aclcb_lock;
+ acl_clean_aclEval_context ( &aclcb->aclcb_eval_context, 0 /* clean*/ );
+ slapi_sdn_free ( &aclcb->aclcb_sdn );
+ aclcb->aclcb_lock = NULL;
+ slapi_ch_free ( (void **) &aclcb );
+
+ PR_Unlock ( shared_lock );
+}
+
+/****************************************************************************/
+/* OPERATION EXTENSION SPECIFIC */
+/****************************************************************************/
+void *
+acl_operation_ext_constructor ( void *object, void *parent )
+{
+ Acl_PBlock *aclpb = NULL;
+
+ TNF_PROBE_0_DEBUG(acl_operation_ext_constructor_start ,"ACL","");
+
+ /* This means internal operations */
+ if ( NULL == parent) {
+
+ TNF_PROBE_1_DEBUG(acl_operation_ext_constructor_end ,"ACL","",
+ tnf_string,internal_op,"");
+
+ return NULL;
+ }
+
+ aclpb = acl__get_aclpb_from_pool();
+ if ( NULL == aclpb ) {
+ slapi_log_error ( SLAPI_LOG_FATAL, plugin_name,
+ "Operation extension allocation Failed\n");
+ }
+
+ TNF_PROBE_0_DEBUG(acl_operation_ext_constructor_end ,"ACL","");
+
+ return aclpb;
+
+}
+
+void
+acl_operation_ext_destructor ( void *ext, void *object, void *parent )
+{
+
+ struct acl_cblock *aclcb = NULL;
+ struct acl_pblock *aclpb = NULL;
+
+ TNF_PROBE_0_DEBUG(acl_operation_ext_destructor_start ,"ACL","");
+
+ if ( (NULL == parent ) || (NULL == ext)) {
+ TNF_PROBE_1_DEBUG(acl_operation_ext_destructor_end ,"ACL","",
+ tnf_string,internal_op,"");
+
+ return;
+ }
+
+ aclpb = (Acl_PBlock *) ext;
+
+ if ( (NULL == aclpb) ||
+ (NULL == aclpb->aclpb_pblock) ||
+ (!(aclpb->aclpb_state & ACLPB_INITIALIZED)))
+ goto clean_aclpb;
+
+ /* get the connection extension */
+ aclcb = (struct acl_cblock *) acl_get_ext ( ACL_EXT_CONNECTION, parent );
+
+ /* We are about to get out of this connection. Move all the
+ ** cached information to the acl private block which hangs
+ ** from the connection struct.
+ */
+ if ( aclcb && aclcb->aclcb_lock &&
+ ( (aclpb->aclpb_state & ACLPB_UPD_ACLCB_CACHE ) ||
+ (aclpb->aclpb_state & ACLPB_INCR_ACLCB_CACHE ) ) ) {
+
+ aclEvalContext *c_evalContext;
+ int attr_only = 0;
+ PRLock *shared_lock = aclcb->aclcb_lock;
+
+ if (aclcb->aclcb_lock ) PR_Lock ( shared_lock );
+ else {
+ goto clean_aclpb;
+ }
+ if ( !aclcb->aclcb_lock ) {
+ slapi_log_error (SLAPI_LOG_FATAL, plugin_name, "aclcb lock released! aclcb cache can't be refreshed\n");
+ PR_Unlock ( shared_lock );
+ goto clean_aclpb;
+ }
+
+ /* We need to refresh the aclcb cache */
+ if ( aclpb->aclpb_state & ACLPB_UPD_ACLCB_CACHE )
+ acl_clean_aclEval_context ( &aclcb->aclcb_eval_context, 0 /* clean*/ );
+ if ( aclpb->aclpb_prev_entryEval_context.acle_numof_attrs ) {
+ c_evalContext = &aclpb->aclpb_prev_entryEval_context;
+ } else {
+ c_evalContext = &aclpb->aclpb_curr_entryEval_context;
+ }
+
+ if (( aclpb->aclpb_state & ACLPB_INCR_ACLCB_CACHE ) &&
+ ! ( aclpb->aclpb_state & ACLPB_UPD_ACLCB_CACHE ))
+ attr_only = 1;
+
+ acl_copyEval_context ( NULL, c_evalContext, &aclcb->aclcb_eval_context, attr_only );
+
+ aclcb->aclcb_aclsignature = aclpb->aclpb_signature;
+ if ( aclcb->aclcb_sdn && aclpb->aclpb_authorization_sdn &&
+ (0 != slapi_sdn_compare ( aclcb->aclcb_sdn,
+ aclpb->aclpb_authorization_sdn ) ) ) {
+ slapi_sdn_set_ndn_byval( aclcb->aclcb_sdn,
+ slapi_sdn_get_ndn ( aclpb->aclpb_authorization_sdn ) );
+ }
+ aclcb->aclcb_state = 0;
+ aclcb->aclcb_state |= ACLCB_HAS_CACHED_EVALCONTEXT;
+
+ PR_Unlock ( shared_lock );
+ }
+
+clean_aclpb:
+ if ( aclpb ) {
+
+ if ( aclpb->aclpb_proxy ) {
+ TNF_PROBE_0_DEBUG(acl_proxy_aclpbdoneback_start ,"ACL","");
+
+ acl__done_aclpb( aclpb->aclpb_proxy );
+
+ /* Put back to the Pool */
+ acl__put_aclpb_back_to_pool ( aclpb->aclpb_proxy );
+ aclpb->aclpb_proxy = NULL;
+ TNF_PROBE_0_DEBUG(acl_proxy_aclpbdoneback_end ,"ACL","");
+
+ }
+
+ TNF_PROBE_0_DEBUG(acl_aclpbdoneback_start ,"ACL","");
+
+ acl__done_aclpb( aclpb);
+ acl__put_aclpb_back_to_pool ( aclpb );
+
+ TNF_PROBE_0_DEBUG(acl_aclpbdoneback_end ,"ACL","");
+
+ }
+
+ TNF_PROBE_0_DEBUG(acl_operation_ext_destructor_end ,"ACL","");
+
+}
+
+/****************************************************************************/
+/* FUNCTIONS TO MANAGE THE ACLPB POOL */
+/****************************************************************************/
+
+/*
+ * Get the right acl pblock
+ */
+struct acl_pblock *
+acl_get_aclpb ( Slapi_PBlock *pb, int type )
+{
+ Acl_PBlock *aclpb = NULL;
+ void *op = NULL;
+
+ slapi_pblock_get ( pb, SLAPI_OPERATION, &op );
+ aclpb = (Acl_PBlock *) acl_get_ext ( ACL_EXT_OPERATION, op );
+ if (NULL == aclpb ) return NULL;
+
+ if ( type == ACLPB_BINDDN_PBLOCK )
+ return aclpb;
+ else if ( type == ACLPB_PROXYDN_PBLOCK )
+ return aclpb->aclpb_proxy;
+ else
+ slapi_log_error ( SLAPI_LOG_FATAL, plugin_name,
+ "acl_get_aclpb: Invalid aclpb type %d\n", type );
+ return NULL;
+}
+/*
+ * Create a new proxy acl pblock
+ *
+ */
+struct acl_pblock *
+acl_new_proxy_aclpb( Slapi_PBlock *pb )
+{
+ void *op;
+ Acl_PBlock *aclpb = NULL;
+ Acl_PBlock *proxy_aclpb = NULL;
+
+ slapi_pblock_get ( pb, SLAPI_OPERATION, &op );
+ aclpb = (Acl_PBlock *) acl_get_ext ( ACL_EXT_OPERATION, op );
+ if (NULL == aclpb ) return NULL;
+
+ proxy_aclpb = acl__get_aclpb_from_pool();
+ if (NULL == proxy_aclpb) return NULL;
+ proxy_aclpb->aclpb_type = ACLPB_TYPE_PROXY;
+
+ aclpb->aclpb_proxy = proxy_aclpb;
+
+ return proxy_aclpb;
+
+}
+static int
+acl__handle_config_entry (Slapi_Entry *e, void *callback_data )
+{
+ *(int * )callback_data = slapi_entry_attr_get_int( e, "nsslapd-threadnumber");
+
+ return 0;
+}
+
+/*
+ * Create a pool of acl pblock. Created during the ACL plugin
+ * initialization.
+ */
+int
+acl_create_aclpb_pool ()
+{
+
+ Acl_PBlock *aclpb;
+ Acl_PBlock *prev_aclpb;
+ Acl_PBlock *first_aclpb;
+ int i;
+ int maxThreads= 0;
+
+ slapi_search_internal_callback( "cn=config", LDAP_SCOPE_BASE, "(objectclass=*)",
+ NULL, 0 /* attrsonly */,
+ &maxThreads/* callback_data */,
+ NULL /* controls */,
+ NULL /* result_callback */,
+ acl__handle_config_entry,
+ NULL /* referral_callback */);
+
+ /* Create a pool pf aclpb */
+ maxThreads = 2 * maxThreads;
+
+ aclQueue = ( Acl_PBqueue *) slapi_ch_calloc ( 1, sizeof (Acl_PBqueue) );
+ aclQueue->aclq_lock = PR_NewLock();
+
+ if ( NULL == aclQueue->aclq_lock ) {
+ /* ERROR */
+ return 1;
+ }
+
+ prev_aclpb = NULL;
+ first_aclpb = NULL;
+ for ( i = 0; i < maxThreads; i++ ) {
+ aclpb = acl__malloc_aclpb ();
+ if ( 0 == i) first_aclpb = aclpb;
+
+ aclpb->aclpb_prev = prev_aclpb;
+ if ( prev_aclpb ) prev_aclpb->aclpb_next = aclpb;
+ prev_aclpb = aclpb;
+ }
+
+ /* Since this is the begining, everybody is in free list */
+ aclQueue->aclq_free = first_aclpb;
+
+ aclQueue->aclq_nfree = maxThreads;
+ return 0;
+}
+
+/*
+ * Get a FREE acl pblock from the pool.
+ *
+ */
+static Acl_PBlock *
+acl__get_aclpb_from_pool ( )
+{
+ Acl_PBlock *aclpb = NULL;
+ Acl_PBlock *t_aclpb = NULL;
+
+
+ PR_Lock (aclQueue->aclq_lock );
+
+ /* Get the first aclpb from the FREE List */
+ aclpb = aclQueue->aclq_free;
+ if ( aclpb ) {
+ t_aclpb = aclpb->aclpb_next;
+ if ( t_aclpb ) t_aclpb->aclpb_prev = NULL;
+ aclQueue->aclq_free = t_aclpb;
+
+ /* make the this an orphon */
+ aclpb->aclpb_prev = aclpb->aclpb_next = NULL;
+
+ aclQueue->aclq_nfree--;
+ } else {
+ slapi_log_error ( SLAPI_LOG_ACL, plugin_name,
+ "Unable to find a free aclpb\n");
+ aclpb = acl__malloc_aclpb ();
+ }
+
+
+ /* Now move it to the FRONT of busy list */
+ t_aclpb = aclQueue->aclq_busy;
+ aclpb->aclpb_next = t_aclpb;
+ if ( t_aclpb ) t_aclpb->aclpb_prev = aclpb;
+ aclQueue->aclq_busy = aclpb;
+ aclQueue->aclq_nbusy++;
+
+ PR_Unlock (aclQueue->aclq_lock );
+
+ return aclpb;
+}
+/*
+ * Put the acl pblock into the FREE pool.
+ *
+ */
+static int
+acl__put_aclpb_back_to_pool ( Acl_PBlock *aclpb )
+{
+
+ Acl_PBlock *p_aclpb, *n_aclpb;
+
+ PR_Lock (aclQueue->aclq_lock );
+
+ /* Remove it from the busy list */
+ n_aclpb = aclpb->aclpb_next;
+ p_aclpb = aclpb->aclpb_prev;
+
+ if ( p_aclpb ) {
+ p_aclpb->aclpb_next = n_aclpb;
+ if ( n_aclpb ) n_aclpb->aclpb_prev = p_aclpb;
+ } else {
+ aclQueue->aclq_busy = n_aclpb;
+ if ( n_aclpb ) n_aclpb->aclpb_prev = NULL;
+ }
+ aclQueue->aclq_nbusy--;
+
+
+ /* Put back to the FREE list */
+ aclpb->aclpb_prev = NULL;
+ n_aclpb = aclQueue->aclq_free;
+ aclpb->aclpb_next = n_aclpb;
+ if ( n_aclpb ) n_aclpb->aclpb_prev = aclpb;
+ aclQueue->aclq_free = aclpb;
+ aclQueue->aclq_nfree++;
+
+ PR_Unlock (aclQueue->aclq_lock );
+
+ return 0;
+}
+
+/*
+ * Allocate the basic acl pb
+ *
+ */
+static Acl_PBlock *
+acl__malloc_aclpb ( )
+{
+ Acl_PBlock *aclpb = NULL;
+
+
+ aclpb = ( Acl_PBlock *) slapi_ch_calloc ( 1, sizeof ( Acl_PBlock) );
+
+ /* Now set the propert we need for ACL evaluations */
+ if ((aclpb->aclpb_proplist = PListNew(NULL)) == NULL) {
+ slapi_log_error (SLAPI_LOG_FATAL, plugin_name,
+ "Unable to allocate the aclprop PList\n");
+ return NULL;
+ }
+
+ if (PListInitProp(aclpb->aclpb_proplist, 0, DS_PROP_ACLPB, aclpb, 0) < 0) {
+ slapi_log_error(SLAPI_LOG_FATAL, plugin_name,
+ "Unable to set the ACL PBLOCK in the Plist\n");
+ return NULL;
+ }
+ if (PListInitProp(aclpb->aclpb_proplist, 0, DS_ATTR_USERDN, aclpb, 0) < 0) {
+ slapi_log_error(SLAPI_LOG_FATAL, plugin_name,
+ "Unable to set the USER DN in the Plist\n");
+ return NULL;
+ }
+ if (PListInitProp(aclpb->aclpb_proplist, 0, DS_ATTR_AUTHTYPE, aclpb, 0) < 0) {
+ slapi_log_error(SLAPI_LOG_FATAL, plugin_name,
+ "Unable to set the AUTH TYPE in the Plist\n");
+ return NULL;
+ }
+ if (PListInitProp(aclpb->aclpb_proplist, 0, DS_ATTR_ENTRY, aclpb, 0) < 0) {
+ slapi_log_error(SLAPI_LOG_FATAL, plugin_name,
+ "Unable to set the ENTRY TYPE in the Plist\n");
+ return NULL;
+ }
+
+ /*
+ * ACL_ATTR_IP and ACL_ATTR_DNS are initialized lazily in the
+ * IpGetter and DnsGetter functions.
+ * They are removed from the aclpb property list at acl__aclpb_done()
+ * time.
+ */
+
+ /* allocate the acleval struct */
+ aclpb->aclpb_acleval = (ACLEvalHandle_t *) ACL_EvalNew(NULL, NULL);
+ if (aclpb->aclpb_acleval == NULL) {
+ slapi_log_error(SLAPI_LOG_FATAL, plugin_name,
+ "Unable to allocate the acleval block\n");
+ return NULL;
+ }
+ /*
+ * This is a libaccess routine.
+ * Need to setup subject and resource property information
+ */
+
+ ACL_EvalSetSubject(NULL, aclpb->aclpb_acleval, aclpb->aclpb_proplist);
+
+ /* allocate some space for attr name */
+ aclpb->aclpb_Evalattr = (char *) slapi_ch_malloc (ACLPB_MAX_ATTR_LEN);
+
+ aclpb->aclpb_deny_handles = (aci_t **) slapi_ch_calloc (1,
+ ACLPB_INCR_LIST_HANDLES * sizeof (aci_t *));
+
+ aclpb->aclpb_allow_handles = (aci_t **) slapi_ch_calloc (1,
+ ACLPB_INCR_LIST_HANDLES * sizeof (aci_t *));
+
+ aclpb->aclpb_deny_handles_size = ACLPB_INCR_LIST_HANDLES;
+ aclpb->aclpb_allow_handles_size = ACLPB_INCR_LIST_HANDLES;
+
+ /* allocate the array for bases */
+ aclpb->aclpb_grpsearchbase = (char **)
+ slapi_ch_malloc (ACLPB_INCR_BASES * sizeof(char *));
+ aclpb->aclpb_grpsearchbase_size = ACLPB_INCR_BASES;
+ aclpb->aclpb_numof_bases = 0;
+
+ /* Make sure aclpb_search_base is initialized to NULL..tested elsewhere! */
+ aclpb->aclpb_search_base = NULL;
+
+ aclpb->aclpb_authorization_sdn = slapi_sdn_new ();
+ aclpb->aclpb_curr_entry_sdn = slapi_sdn_new();
+
+ aclpb->aclpb_aclContainer = acllist_get_aciContainer_new ();
+
+ /* hash table to store macro matched values from targets */
+ aclpb->aclpb_macro_ht = acl_ht_new();
+
+ return aclpb;
+
+}
+
+/* Initializes the aclpb */
+void
+acl_init_aclpb ( Slapi_PBlock *pb , Acl_PBlock *aclpb, const char *dn, int copy_from_aclcb)
+{
+ struct acl_cblock *aclcb = NULL;
+ char *authType;
+ void *conn;
+ unsigned long op_type;
+
+
+ if ( NULL == aclpb ) {
+ slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, "acl_init_aclpb:No ACLPB\n");
+ return;
+ }
+
+ /* See if we have initialized already */
+ if (aclpb->aclpb_state & ACLPB_INITIALIZED) return;
+
+ slapi_pblock_get ( pb, SLAPI_OPERATION_TYPE, &op_type );
+ if ( op_type == SLAPI_OPERATION_BIND || op_type == SLAPI_OPERATION_UNBIND )
+ return;
+
+ /* We indicate the initialize here becuase, if something goes wrong, it's cleaned up
+ ** properly.
+ */
+ aclpb->aclpb_state = ACLPB_INITIALIZED;
+
+ /* We make an anonymous user a non null dn which is empty */
+ if (dn && *dn != '\0' )
+ slapi_sdn_set_ndn_byval ( aclpb->aclpb_authorization_sdn, dn );
+ else
+ slapi_sdn_set_ndn_byval ( aclpb->aclpb_authorization_sdn, "" );
+
+ /* reset scoped entry cache to be empty */
+ aclpb->aclpb_scoped_entry_anominfo.anom_e_nummatched = 0;
+
+ if (PListAssignValue(aclpb->aclpb_proplist, DS_ATTR_USERDN,
+ slapi_sdn_get_ndn(aclpb->aclpb_authorization_sdn), 0) < 0) {
+ slapi_log_error(SLAPI_LOG_FATAL, plugin_name,
+ "Unable to set the USER DN in the Plist\n");
+ return;
+ }
+ slapi_pblock_get ( pb, SLAPI_OPERATION_AUTHTYPE, &authType );
+ if (PListAssignValue(aclpb->aclpb_proplist, DS_ATTR_AUTHTYPE, authType, 0) < 0) {
+ slapi_log_error(SLAPI_LOG_FATAL, plugin_name,
+ "Unable to set the AUTH TYPE in the Plist\n");
+ return;
+ }
+ /* PKBxxx: We should be getting it from the OP struct */
+ slapi_pblock_get ( pb, SLAPI_CONN_CERT, &aclpb->aclpb_clientcert );
+
+ /* See if the we have already a cached info about user's group */
+ aclg_init_userGroup ( aclpb, dn, 0 /* get lock */ );
+
+ slapi_pblock_get( pb, SLAPI_BE_MAXNESTLEVEL, &aclpb->aclpb_max_nesting_level );
+ slapi_pblock_get( pb, SLAPI_SEARCH_SIZELIMIT, &aclpb->aclpb_max_member_sizelimit );
+ if ( aclpb->aclpb_max_member_sizelimit == 0 ) {
+ aclpb->aclpb_max_member_sizelimit = SLAPD_DEFAULT_LOOKTHROUGHLIMIT;
+ }
+ slapi_pblock_get( pb, SLAPI_OPERATION_TYPE, &aclpb->aclpb_optype );
+
+ aclpb->aclpb_signature = acl_get_aclsignature();
+ aclpb->aclpb_last_cache_result = 0;
+ aclpb->aclpb_pblock = pb;
+ PR_ASSERT ( aclpb->aclpb_pblock != NULL );
+
+ /* get the connection */
+ slapi_pblock_get ( pb, SLAPI_CONNECTION, &conn);
+ aclcb = (struct acl_cblock *) acl_get_ext ( ACL_EXT_CONNECTION, conn );
+
+ if (NULL == aclcb || NULL == aclcb->aclcb_lock) {
+ /* This could happen if the client is dead and we are in
+ ** process of abondoning this operation
+ */
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "No CONNECTION extension\n");
+
+ } else if ( aclcb->aclcb_state == -1 ) {
+ /* indicate that we need to update the cache */
+ aclpb->aclpb_state |= ACLPB_UPD_ACLCB_CACHE;
+ aclcb->aclcb_state = 0; /* Nore this is ACLCB and not ACLPB */
+
+ } else if ( copy_from_aclcb ){
+ char *cdn;
+ Slapi_DN *c_sdn; /* client SDN */
+
+ /* check if the operation is abandoned or not.*/
+ if ( slapi_op_abandoned ( pb ) ) {
+ return;
+ }
+
+ slapi_pblock_get ( pb, SLAPI_CONN_DN, &cdn ); /* We *must* free cdn! */
+ c_sdn = slapi_sdn_new_dn_passin( cdn );
+ PR_Lock ( aclcb->aclcb_lock );
+ /*
+ * since PR_Lock is taken,
+ * we can mark the connection extension ok to be destroyed.
+ */
+ if ( (aclcb->aclcb_aclsignature != acl_get_aclsignature()) ||
+ ( (NULL == cdn) && aclcb->aclcb_sdn ) ||
+ (cdn && (NULL == aclcb->aclcb_sdn )) ||
+ (cdn && aclcb->aclcb_sdn && ( 0 != slapi_sdn_compare ( c_sdn, aclcb->aclcb_sdn ) ))) {
+
+ /* cleanup the aclcb cache */
+ acl_clean_aclEval_context ( &aclcb->aclcb_eval_context, 0 /*clean*/ );
+ aclcb->aclcb_state = 0;
+ aclcb->aclcb_aclsignature = 0;
+ slapi_sdn_done ( aclcb->aclcb_sdn );
+ }
+ slapi_sdn_free ( &c_sdn );
+
+ /* COPY the cached information from ACLCB --> ACLPB */
+ if ( aclcb->aclcb_state & ACLCB_HAS_CACHED_EVALCONTEXT) {
+ acl_copyEval_context ( aclpb, &aclcb->aclcb_eval_context ,
+ &aclpb->aclpb_prev_opEval_context, 0 );
+ aclpb->aclpb_state |= ACLPB_HAS_ACLCB_EVALCONTEXT;
+ }
+ PR_Unlock ( aclcb->aclcb_lock );
+ }
+
+}
+
+/* Cleans up the aclpb */
+static void
+acl__done_aclpb ( struct acl_pblock *aclpb )
+{
+
+ int i;
+ int dump_aclpb_info = 0;
+ char *ds_attr_userdn=NULL; /* for finding userdn for freeing */
+ int rc=-1;
+ char *tmp_ptr=NULL;
+
+ /*
+ ** First, let's do some sanity checks to see if we have everything what
+ ** it should be.
+ */
+
+ /* Nothing needs to be cleaned up in this case */
+ if ( !aclpb->aclpb_state & ACLPB_INITIALIZED)
+ return;
+
+ /* Check the state */
+ if (aclpb->aclpb_state & ~ACLPB_STATE_ALL) {
+ slapi_log_error( SLAPI_LOG_FATAL, plugin_name,
+ "The aclpb.state value (%d) is incorrect. Exceeded the limit (%d)\n",
+ aclpb->aclpb_state, ACLPB_STATE_ALL);
+ dump_aclpb_info = 1;
+
+ }
+
+ /* acl__dump_stats ( aclpb, acl__get_aclpb_type(aclpb)); */
+
+ /* reset the usergroup cache */
+ aclg_reset_userGroup ( aclpb );
+
+ if ( aclpb->aclpb_res_type & ~ACLPB_RESTYPE_ALL ) {
+ slapi_log_error( SLAPI_LOG_FATAL, plugin_name,
+ "The aclpb res_type value (%d) has exceeded. Limit is (%d)\n",
+ aclpb->aclpb_res_type, ACLPB_RESTYPE_ALL, 0 );
+ dump_aclpb_info = 1;
+ }
+
+ if ( dump_aclpb_info ) {
+ const char *ndn;
+ slapi_log_error ( SLAPI_LOG_FATAL, plugin_name,
+ "ACLPB value is:%p\n", aclpb, 0,0 );
+
+ ndn = slapi_sdn_get_ndn ( aclpb->aclpb_curr_entry_sdn );
+ slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, "curr_entry:%p num_entries:%d curr_dn:%p\n",
+ aclpb->aclpb_curr_entry ? (char *) aclpb->aclpb_curr_entry : "NULL",
+ aclpb->aclpb_num_entries,
+ ndn ? ndn : "NULL");
+
+ slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, "Last attr:%p, Plist:%p acleval: %p\n",
+ aclpb->aclpb_Evalattr ? aclpb->aclpb_Evalattr : "NULL",
+ aclpb->aclpb_proplist ? (char *) aclpb->aclpb_proplist : "NULL",
+ aclpb->aclpb_acleval ? (char *) aclpb->aclpb_acleval : "NULL" );
+ }
+
+ /* Now Free the contents or clean it */
+ slapi_sdn_done ( aclpb->aclpb_curr_entry_sdn );
+ if (aclpb->aclpb_Evalattr)
+ aclpb->aclpb_Evalattr[0] = '\0';
+
+ /* deallocate the contents of the base array */
+ for (i=0; i < aclpb->aclpb_numof_bases; i++) {
+ if (aclpb->aclpb_grpsearchbase[i])
+ slapi_ch_free ( (void **)&aclpb->aclpb_grpsearchbase[i] );
+ }
+ aclpb->aclpb_numof_bases = 0;
+
+ acl_clean_aclEval_context ( &aclpb->aclpb_prev_opEval_context, 0 /*claen*/ );
+ acl_clean_aclEval_context ( &aclpb->aclpb_prev_entryEval_context, 0 /*clean*/ );
+ acl_clean_aclEval_context ( &aclpb->aclpb_curr_entryEval_context, 0/*clean*/ );
+
+ if ( aclpb->aclpb_client_entry ) slapi_entry_free ( aclpb->aclpb_client_entry );
+ aclpb->aclpb_client_entry = NULL;
+
+ slapi_sdn_done ( aclpb->aclpb_authorization_sdn );
+ aclpb->aclpb_pblock = NULL;
+
+ if ( aclpb->aclpb_search_base )
+ slapi_ch_free ( (void **) &aclpb->aclpb_search_base );
+ for ( i=0; i < aclpb->aclpb_num_deny_handles; i++ )
+ aclpb->aclpb_deny_handles[i] = NULL;
+ aclpb->aclpb_num_deny_handles = 0;
+
+ for ( i=0; i < aclpb->aclpb_num_allow_handles; i++ )
+ aclpb->aclpb_allow_handles[i] = NULL;
+ aclpb->aclpb_num_allow_handles = 0;
+
+ /* clear results cache */
+ memset((char*)aclpb->aclpb_cache_result, 0,
+ sizeof(struct result_cache)*aclpb->aclpb_last_cache_result);
+ aclpb->aclpb_last_cache_result = 0;
+ aclpb->aclpb_handles_index[0] = -1;
+ aclpb->aclpb_base_handles_index[0] = -1;
+
+ aclpb->aclpb_stat_acllist_scanned = 0;
+ aclpb->aclpb_stat_aclres_matched = 0;
+ aclpb->aclpb_stat_total_entries = 0;
+ aclpb->aclpb_stat_anom_list_scanned = 0;
+ aclpb->aclpb_stat_num_copycontext = 0;
+ aclpb->aclpb_stat_num_copy_attrs = 0;
+ aclpb->aclpb_stat_num_tmatched_acls = 0;
+
+ aclpb->aclpb_clientcert = NULL;
+ aclpb->aclpb_proxy = NULL;
+
+ acllist_done_aciContainer ( aclpb->aclpb_aclContainer );
+
+ /*
+ * Here, decide which things need to be freed/removed/whatever from the
+ * aclpb_proplist.
+ */
+
+ /*
+ * The DS_ATTR_DNS property contains the name of the client machine.
+ *
+ * The value pointed to by this property is stored in the pblock--it
+ * points to the SLAPI_CLIENT_DNS object. So, that memory will
+ * be freed elsewhere.
+ *
+ * It's removed here from the aclpb_proplist as it would be an error to
+ * allow it to persist in the aclpb which is an operation time thing.
+ * If we leave it here the next time this aclpb gets used, the DnsGetter
+ * is not called by LASDnsEval/ACL_GetAttribute() as it thinks the
+ * ACL_ATTR_DNS has already been initialized.
+ *
+ */
+
+ if ((rc = PListFindValue(aclpb->aclpb_proplist, ACL_ATTR_DNS,
+ (void **)&tmp_ptr, NULL)) > 0) {
+
+ PListDeleteProp(aclpb->aclpb_proplist, rc, NULL);
+ }
+
+ /*
+ * Remove the DS_ATTR_IP property from the property list.
+ * The value of this property is just the property pointer
+ * (an unsigned long) so that gets freed too when we delete the
+ * property.
+ * It's removed here from the aclpb_proplist as it would be an error to
+ * allow it to persist in the aclpb which is an operation time thing.
+ * If we leave it here the next time this aclpb gets used, the DnsGetter
+ * is not called by LASIpEval/ACL_GetAttribute() as it thinks the
+ * ACL_ATTR_IP has already been initialized.
+ */
+
+ if ((rc = PListFindValue(aclpb->aclpb_proplist, ACL_ATTR_IP,
+ (void **)&tmp_ptr, NULL)) > 0) {
+
+ PListDeleteProp(aclpb->aclpb_proplist, rc, NULL);
+ }
+
+ /*
+ * The DS_ATTR_USERDN value comes from aclpb_authorization_sdn.
+ * This memory
+ * is freed above using aclpb_authorization_sdn so we don't need to free it here
+ * before overwriting the old value.
+ */
+ PListAssignValue(aclpb->aclpb_proplist, DS_ATTR_USERDN, NULL, 0);
+
+ /*
+ * The DS_ATTR_AUTHTYPE value is a pointer into the pblock, so
+ * we do not need to free that memory before overwriting the value.
+ */
+ PListAssignValue(aclpb->aclpb_proplist, DS_ATTR_AUTHTYPE, NULL, 0);
+
+ /*
+ * DO NOT overwrite the aclpb pointer--it is initialized at malloc_aclpb
+ * time and is kept within the aclpb.
+ *
+ * PListAssignValue(aclpb->aclpb_proplist, DS_PROP_ACLPB, NULL, 0);
+ */
+
+ /*
+ * The DS_ATTR_ENTRY value was a pointer to the entry being evaluated
+ * by the ACL code. That entry comes from outside the context of
+ * the acl code and so is dealt with out there. Ergo, here we can just
+ * lose the pointer to that entry.
+ */
+ PListAssignValue(aclpb->aclpb_proplist, DS_ATTR_ENTRY, NULL, 0);
+
+ aclpb->aclpb_signature = 0;
+
+ /* reset scoped entry cache to be empty */
+ aclpb->aclpb_scoped_entry_anominfo.anom_e_nummatched = 0;
+
+ /* Free up any of the string values left in the macro ht and remove
+ * the entries.*/
+ acl_ht_free_all_entries_and_values(aclpb->aclpb_macro_ht);
+
+ /* Finally, set it to the no use state */
+ aclpb->aclpb_state = 0;
+
+}
+
+static char *
+acl__get_aclpb_type ( Acl_PBlock *aclpb )
+{
+
+ if (aclpb->aclpb_state & ACLPB_TYPE_PROXY)
+ return ACLPB_TYPE_PROXY_STR;
+
+ return ACLPB_TYPE_MAIN_STR;
+}
+static void
+acl__dump_stats ( struct acl_pblock *aclpb , const char *block_type)
+{
+ int connid = 0;
+ int opid = 0;
+ Slapi_PBlock *pb = NULL;
+
+ pb = aclpb->aclpb_pblock;
+ if ( pb ) {
+ slapi_pblock_get ( pb, SLAPI_CONN_ID, &connid );
+ slapi_pblock_get ( pb, SLAPI_OPERATION_ID, &opid );
+ }
+
+ /* DUMP STAT INFO */
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "**** ACL OPERATION STAT BEGIN ( aclpb:%p Block type: %s): Conn:%d Operation:%d *******\n",
+ aclpb, block_type, connid, opid );
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name, "\tNumber of entries scanned: %d\n",
+ aclpb->aclpb_stat_total_entries);
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name, "\tNumber of times ACL List scanned: %d\n",
+ aclpb->aclpb_stat_acllist_scanned);
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name, "\tNumber of ACLs with target matched:%d\n",
+ aclpb->aclpb_stat_num_tmatched_acls);
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name, "\tNumber of times acl resource matched:%d\n",
+ aclpb->aclpb_stat_aclres_matched);
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name, "\tNumber of times ANOM list scanned:%d\n",
+ aclpb->aclpb_stat_anom_list_scanned);
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name, "\tNumber of times Context was copied:%d\n",
+ aclpb->aclpb_stat_num_copycontext);
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name, "\tNumber of times Attrs was copied:%d\n",
+ aclpb->aclpb_stat_num_copy_attrs);
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name, " **** ACL OPERATION STAT END *******\n");
+}
+/****************************************************************************/
+/* E N D */
+/****************************************************************************/
+
diff --git a/ldap/servers/plugins/acl/aclanom.c b/ldap/servers/plugins/acl/aclanom.c
new file mode 100644
index 00000000..71b0c68a
--- /dev/null
+++ b/ldap/servers/plugins/acl/aclanom.c
@@ -0,0 +1,536 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include "acl.h"
+
+/************************************************************************
+Anonymous profile
+**************************************************************************/
+
+struct anom_targetacl {
+ int anom_type; /* defines for anom types same as aci_type */
+ int anom_access;
+ Slapi_DN *anom_target; /* target of the ACL */
+ Slapi_Filter *anom_filter; /* targetfilter part */
+ char **anom_targetAttrs; /* list of attrs */
+};
+
+
+struct anom_profile {
+ short anom_signature;
+ short anom_numacls;
+ struct anom_targetacl anom_targetinfo[ACL_ANOM_MAX_ACL];
+};
+
+static struct anom_profile *acl_anom_profile = NULL;
+
+static PRRWLock *anom_rwlock = NULL;
+#define ANOM_LOCK_READ() PR_RWLock_Rlock (anom_rwlock )
+#define ANOM_UNLOCK_READ() PR_RWLock_Unlock (anom_rwlock )
+#define ANOM_LOCK_WRITE() PR_RWLock_Wlock (anom_rwlock )
+#define ANOM_UNLOCK_WRITE() PR_RWLock_Unlock (anom_rwlock )
+
+
+static void __aclanom__del_profile ();
+
+/*
+ * aclanom_init ();
+ * Generate a profile for the anonymous user. We can use this profile
+ * later to determine what resources the client is allowed to.
+ *
+ * Dependency:
+ * Before calling this, it is assumed that all the ACLs have been read
+ * and parsed.
+ *
+ * We will go thru all the ACL and pick the ANYONE ACL and generate the anom
+ * profile.
+ *
+ */
+int
+aclanom_init ()
+{
+
+ acl_anom_profile = (struct anom_profile * )
+ slapi_ch_calloc (1, sizeof ( struct anom_profile ) );
+
+ if (( anom_rwlock = PR_NewRWLock( PR_RWLOCK_RANK_NONE,"ANOM LOCK") ) == NULL ) {
+ slapi_log_error( SLAPI_LOG_FATAL, plugin_name,
+ "Failed in getting the ANOM rwlock\n" );
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * Depending on the context, this routine may need to take the
+ * acicache read lock.
+*/
+void
+aclanom_gen_anomProfile (acl_lock_flag_t lock_flag)
+{
+ aci_t *aci = NULL;
+ int i;
+ Targetattr **srcattrArray;
+ Targetattr *attr;
+ struct anom_profile *a_profile;
+ PRUint32 cookie;
+
+ PR_ASSERT( lock_flag == DO_TAKE_ACLCACHE_READLOCK ||
+ lock_flag == DONT_TAKE_ACLCACHE_READLOCK);
+
+ /*
+ * This routine requires two locks:
+ * the one for the global cache in acllist_acicache_READ_LOCK() and
+ * the one for the anom profile.
+ * They _must_ be taken in the order presented here or there
+ * is a deadlock scenario with acllist_remove_aci_needsLock() which
+ * takes them is this order.
+ */
+
+ if ( lock_flag == DO_TAKE_ACLCACHE_READLOCK ) {
+ acllist_acicache_READ_LOCK();
+ }
+ ANOM_LOCK_WRITE ();
+ a_profile = acl_anom_profile;
+
+ if ( (!acl_get_aclsignature()) || ( !a_profile) ||
+ (a_profile->anom_signature == acl_get_aclsignature()) ) {
+ ANOM_UNLOCK_WRITE ();
+ if ( lock_flag == DO_TAKE_ACLCACHE_READLOCK ) {
+ acllist_acicache_READ_UNLOCK();
+ }
+ return;
+ }
+
+ /* D0 we have one already. If we do, then clean it up */
+ __aclanom__del_profile();
+
+ /* We have a new signature now */
+ a_profile->anom_signature = acl_get_aclsignature();
+
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name, "GENERATING ANOM USER PROFILE\n", 0,0,0);
+ /*
+ ** Go thru the ACL list and find all the ACLs which apply to the
+ ** anonymous user i.e anyone. we can generate a profile for that.
+ ** We will llok at the simple case i.e it matches
+ ** cases not handled:
+ ** 1) When there is a mix if rule types ( allows & denies )
+ **
+ */
+
+ aci = acllist_get_first_aci ( NULL, &cookie );
+ while ( aci ) {
+ int a_numacl;
+ struct slapi_filter *f;
+ char **destattrArray;
+
+
+ /*
+ * We must not have a rule like: deny ( all ) userdn != "xyz"
+ * or groupdn !=
+ */
+ if ( (aci->aci_type & ACI_HAS_DENY_RULE) &&
+ ( (aci->aci_type & ACI_CONTAIN_NOT_USERDN ) ||
+ (aci->aci_type & ACI_CONTAIN_NOT_GROUPDN) ||
+ (aci->aci_type & ACI_CONTAIN_NOT_ROLEDN)) ){
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+ "CANCELLING ANOM USER PROFILE BECAUSE OF DENY RULE\n", 0,0,0);
+ goto cleanup;
+ }
+
+ /* Must be a anyone rule */
+ if ( aci->aci_elevel != ACI_ELEVEL_USERDN_ANYONE ) {
+ aci = acllist_get_next_aci ( NULL, aci, &cookie);
+ continue;
+ }
+ if (! (aci->aci_access & ( SLAPI_ACL_READ | SLAPI_ACL_SEARCH)) ) {
+ aci = acllist_get_next_aci ( NULL, aci, &cookie);
+ continue;
+ }
+ /* If the rule has anything other than userdn = "ldap:///anyone"
+ ** let's not consider complex rules - let's make this lean.
+ */
+ if ( aci->aci_ruleType & ~ACI_USERDN_RULE ){
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+ "CANCELLING ANOM USER PROFILE BECAUSE OF COMPLEX RULE\n", 0,0,0);
+ goto cleanup;
+ }
+
+ /* Must not be a or have a
+ ** 1 ) DENY RULE 2) targetfilter
+ ** 3) no target pattern ( skip monitor acl )
+ */
+ if ( aci->aci_type & ( ACI_HAS_DENY_RULE | ACI_TARGET_PATTERN |
+ ACI_TARGET_NOT | ACI_TARGET_FILTER_NOT )) {
+ const char *dn = slapi_sdn_get_dn ( aci->aci_sdn );
+
+ /* see if this is a monitor acl */
+ if (( strcasecmp ( dn, "cn=monitor") == 0 ) ||
+ ( strcasecmp ( dn, "cn=monitor,cn=ldbm") == 0 )) {
+ aci = acllist_get_next_aci ( NULL, aci, &cookie);
+ continue;
+ } else {
+ /* clean up before leaving */
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+ "CANCELLING ANOM USER PROFILE 1\n", 0,0,0);
+ goto cleanup;
+ }
+
+ }
+
+ /* Now we have an ALLOW ACL which applies to anyone */
+ a_numacl = a_profile->anom_numacls++;
+
+ if ( a_profile->anom_numacls == ACL_ANOM_MAX_ACL ) {
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name, "CANCELLING ANOM USER PROFILE 2\n", 0,0,0);
+ goto cleanup;
+ }
+
+ if ( (f = aci->target) != NULL ) {
+ char *avaType;
+ struct berval *avaValue;
+ slapi_filter_get_ava ( f, &avaType, &avaValue );
+
+ a_profile->anom_targetinfo[a_numacl].anom_target =
+ slapi_sdn_new_dn_byval ( avaValue->bv_val );
+ } else {
+ a_profile->anom_targetinfo[a_numacl].anom_target =
+ slapi_sdn_dup ( aci->aci_sdn );
+ }
+
+ a_profile->anom_targetinfo[a_numacl].anom_filter = NULL;
+ if ( aci->targetFilterStr )
+ a_profile->anom_targetinfo[a_numacl].anom_filter = slapi_str2filter ( aci->targetFilterStr );
+
+ i = 0;
+ srcattrArray = aci->targetAttr;
+ while ( srcattrArray[i])
+ i++;
+
+ a_profile->anom_targetinfo[a_numacl].anom_targetAttrs =
+ (char **) slapi_ch_calloc ( 1, (i+1) * sizeof(char *));
+
+ srcattrArray = aci->targetAttr;
+ destattrArray = a_profile->anom_targetinfo[a_numacl].anom_targetAttrs;
+
+ i = 0;
+ while ( srcattrArray[i] ) {
+ attr = srcattrArray[i];
+ if ( attr->attr_type & ACL_ATTR_FILTER ) {
+ /* Do'nt want to support these kind now */
+ destattrArray[i] = NULL;
+ /* clean up before leaving */
+ __aclanom__del_profile ();
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+ "CANCELLING ANOM USER PROFILE 3\n", 0,0,0);
+ goto cleanup;
+ }
+
+ destattrArray[i] = slapi_ch_strdup ( attr->u.attr_str );
+ i++;
+ }
+
+ destattrArray[i] = NULL;
+
+ aclutil_print_aci ( aci, "anom" );
+ /* Here we are storing att the info from the acls. However
+ ** we are only interested in a few things like ACI_TARGETATTR_NOT.
+ */
+ a_profile->anom_targetinfo[a_numacl].anom_type = aci->aci_type;
+ a_profile->anom_targetinfo[a_numacl].anom_access = aci->aci_access;
+
+ aci = acllist_get_next_aci ( NULL, aci, &cookie);
+ }
+
+ ANOM_UNLOCK_WRITE ();
+ if ( lock_flag == DO_TAKE_ACLCACHE_READLOCK ) {
+ acllist_acicache_READ_UNLOCK();
+ }
+ return;
+
+cleanup:
+ __aclanom__del_profile ();
+ ANOM_UNLOCK_WRITE ();
+ if ( lock_flag == DO_TAKE_ACLCACHE_READLOCK ) {
+ acllist_acicache_READ_UNLOCK();
+ }
+}
+
+
+void
+aclanom_invalidateProfile ()
+{
+ ANOM_LOCK_WRITE();
+ if ( acl_anom_profile && acl_anom_profile->anom_numacls )
+ acl_anom_profile->anom_signature = 0;
+ ANOM_UNLOCK_WRITE();
+
+
+}
+
+/*
+ * __aclanom_del_profile
+ *
+ * Cleanup the anonymous user's profile we have.
+ *
+ * ASSUMPTION: A WRITE LOCK HAS BEEN OBTAINED
+ *
+ */
+static void
+__aclanom__del_profile (void)
+{
+ int i;
+ struct anom_profile *a_profile;
+
+
+ if ( (a_profile = acl_anom_profile) == NULL ) {
+ return;
+ }
+
+ for ( i=0; i < a_profile->anom_numacls; i++ ) {
+ int j = 0;
+ char **destArray = a_profile->anom_targetinfo[i].anom_targetAttrs;
+
+ /* Deallocate target */
+ slapi_sdn_free ( &a_profile->anom_targetinfo[i].anom_target );
+
+ /* Deallocate filter */
+ if ( a_profile->anom_targetinfo[i].anom_filter )
+ slapi_filter_free ( a_profile->anom_targetinfo[i].anom_filter, 1 );
+
+ /* Deallocate attrs */
+ if ( destArray ) {
+ while ( destArray[j] ) {
+ slapi_ch_free ( (void **) &destArray[j] );
+ j++;
+ }
+ slapi_ch_free ( (void **) &destArray );
+ }
+ a_profile->anom_targetinfo[i].anom_targetAttrs = NULL;
+ a_profile->anom_targetinfo[i].anom_type = 0;
+ a_profile->anom_targetinfo[i].anom_access = 0;
+ }
+ a_profile->anom_numacls = 0;
+
+ /* Don't clean the signatue */
+}
+
+/*
+ * This routine sets up a "context" for evaluation of access control
+ * on a given entry for an anonymous user.
+ * It just factors out the scope and targetfilter info into a list
+ * of indices of the global anom profile list, that apply to this
+ * entry, and stores them in the aclpb.
+ * It's use relies on the way that access control is checked in the mailine search
+ * code in the core server, namely: check filter, check entry, then check each
+ * attribute. So, we call this in acl_access_allowed() before calling
+ * aclanom_match_profile()--therafter, aclanom_match_profile() uses the
+ * context to evaluate access to the entry and attributes.
+ *
+ * If there are no anom profiles, or the anom profiles get cancelled
+ * due to complex anon acis, then that's OK, aclanom_match_profile()
+ * returns -1 and the mainline acl code kicks in.
+ *
+ * The lifetime of this context info is the time it takes to check
+ * access control for all parts of this entry (filter, entry, attributes).
+ * So, if for an example an entry changes and a given anom profile entry
+ * no longer applies, we will not notice until the next round of access
+ * control checking on the entry--this is acceptable.
+ *
+ * The gain on doing this factoring in the following type of search
+ * was approx 6%:
+ * anon bind, 20 threads, exact match, ~20 attributes returned,
+ * (searchrate & DirectoryMark).
+ *
+*/
+void
+aclanom_get_suffix_info(Slapi_Entry *e,
+ struct acl_pblock *aclpb ) {
+ int i;
+ char *ndn = NULL;
+ Slapi_DN *e_sdn;
+ const char *aci_ndn;
+ int populate = 0;
+ struct scoped_entry_anominfo *s_e_anominfo =
+ &aclpb->aclpb_scoped_entry_anominfo;
+
+ ANOM_LOCK_READ ();
+
+ s_e_anominfo->anom_e_nummatched=0;
+
+ ndn = slapi_entry_get_ndn ( e ) ;
+ e_sdn= slapi_entry_get_sdn ( e ) ;
+ for (i=acl_anom_profile->anom_numacls-1; i >= 0; i-- ) {
+ aci_ndn = slapi_sdn_get_ndn (acl_anom_profile->anom_targetinfo[i].anom_target);
+ if (!slapi_sdn_issuffix(e_sdn,acl_anom_profile->anom_targetinfo[i].anom_target)
+ || (!slapi_is_rootdse(ndn) && slapi_is_rootdse(aci_ndn)))
+ continue;
+ if ( acl_anom_profile->anom_targetinfo[i].anom_filter ) {
+ if ( slapi_vattr_filter_test( aclpb->aclpb_pblock, e,
+ acl_anom_profile->anom_targetinfo[i].anom_filter,
+ 0 /*don't do acess chk*/) != 0)
+ continue;
+ }
+ s_e_anominfo->anom_e_targetInfo[s_e_anominfo->anom_e_nummatched]=i;
+ s_e_anominfo->anom_e_nummatched++;
+ }
+ ANOM_UNLOCK_READ ();
+}
+
+
+/*
+ * aclanom_match_profile
+ * Look at the anonymous profile and see if we can use it or not.
+ *
+ *
+ * Inputs:
+ * Slapi_Pblock - The Pblock
+ * Slapi_Entry *e - The entry for which we are asking permission.
+ * char *attr - Attribute name
+ * int access - access type
+ *
+ * Return:
+ * LDAP_SUCCESS ( 0 ) - acess is allowed.
+ * LDAP_INSUFFICIENT_ACCESS (50 ) - access denied.
+ * -1 - didn't match the targets
+ *
+ * Assumptions:
+ * The caller of this module has to make sure that the client is
+ * an anonymous client.
+ */
+int
+aclanom_match_profile (Slapi_PBlock *pb, struct acl_pblock *aclpb, Slapi_Entry *e,
+ char *attr, int access )
+{
+
+ struct anom_profile *a_profile;
+ int result, i, k;
+ char **destArray;
+ int tmatched = 0;
+ char ebuf[ BUFSIZ ];
+ int loglevel;
+ struct scoped_entry_anominfo *s_e_anominfo =
+ &aclpb->aclpb_scoped_entry_anominfo;
+
+ loglevel = slapi_is_loglevel_set(SLAPI_LOG_ACL) ? SLAPI_LOG_ACL : SLAPI_LOG_ACLSUMMARY;
+
+ /* WE are only interested for READ/SEARCH */
+ if ( !(access & ( SLAPI_ACL_SEARCH | SLAPI_ACL_READ)) )
+ return -1;
+
+ /* If we are here means, the client is doing a anonymous read/search */
+ if ((a_profile = acl_anom_profile) == NULL ) {
+ return -1;
+ }
+
+ ANOM_LOCK_READ ();
+ /* Check the signature first */
+ if ( a_profile->anom_signature != acl_get_aclsignature () ) {
+ /* Need to regenrate the signature.
+ * Need a WRITE lock to generate the anom profile -
+ * which is obtained in acl__gen_anom_user_profile (). Since
+ * I don't have upgrade lock -- I have to do this way.
+ */
+ ANOM_UNLOCK_READ ();
+ aclanom_gen_anomProfile (DO_TAKE_ACLCACHE_READLOCK);
+ aclanom_get_suffix_info(e, aclpb );
+ ANOM_LOCK_READ ();
+ }
+
+ /* doing this early saves use a malloc/free/normalize cost */
+ if ( !a_profile->anom_numacls ) {
+ ANOM_UNLOCK_READ ();
+ return -1;
+ }
+
+ result = LDAP_INSUFFICIENT_ACCESS;
+
+ for ( k=0; k<s_e_anominfo->anom_e_nummatched; k++ ) {
+ short matched = 0;
+ short j = 0;
+
+ i = s_e_anominfo->anom_e_targetInfo[k];
+
+ /* Check for right */
+ if ( !(a_profile->anom_targetinfo[i].anom_access & access) )
+ continue;
+
+ /*
+ * XXX rbyrne Don't really understand the role of this
+ * but not causing any obvious bugs...get back to it.
+ */
+ tmatched++;
+
+ if ( attr == NULL ) {
+ result = LDAP_SUCCESS;
+ break;
+ }
+
+ destArray = a_profile->anom_targetinfo[i].anom_targetAttrs;
+ while ( destArray[j] ) {
+ if ( strcasecmp ( destArray[j], "*") == 0 ||
+ slapi_attr_type_cmp ( attr, destArray[j], 1 ) == 0 ) {
+ matched = 1;
+ break;
+ }
+ j++;
+ }
+
+ if ( a_profile->anom_targetinfo[i].anom_type & ACI_TARGET_ATTR_NOT )
+ result = matched ? LDAP_INSUFFICIENT_ACCESS : LDAP_SUCCESS;
+ else
+ result = matched ? LDAP_SUCCESS : LDAP_INSUFFICIENT_ACCESS;
+
+ if ( result == LDAP_SUCCESS )
+ break;
+ } /* for */
+
+ if ( slapi_is_loglevel_set(loglevel) ) {
+ char *ndn = NULL;
+ Slapi_Operation *op = NULL;
+
+ ndn = slapi_entry_get_ndn ( e ) ;
+ slapi_pblock_get(pb, SLAPI_OPERATION, &op);
+
+ if ( result == LDAP_SUCCESS) {
+ const char *aci_ndn;
+ aci_ndn = slapi_sdn_get_ndn (acl_anom_profile->anom_targetinfo[i].anom_target);
+
+ slapi_log_error(loglevel, plugin_name,
+ "conn=%d op=%d: Allow access on entry(%s).attr(%s) to anonymous: acidn=\"%s\"\n",
+ op->o_connid, op->o_opid,
+ escape_string_with_punctuation(ndn, ebuf),
+ attr ? attr:"NULL",
+ escape_string_with_punctuation(aci_ndn, ebuf));
+ } else {
+ slapi_log_error(loglevel, plugin_name,
+ "conn=%d op=%d: Deny access on entry(%s).attr(%s) to anonymous\n",
+ op->o_connid, op->o_opid,
+ escape_string_with_punctuation(ndn, ebuf), attr ? attr:"NULL" );
+ }
+ }
+
+ ANOM_UNLOCK_READ ();
+ if ( tmatched == 0)
+ return -1;
+ else
+ return result;
+
+}
+int
+aclanom_is_client_anonymous ( Slapi_PBlock *pb )
+{
+ char *clientDn;
+
+
+ slapi_pblock_get ( pb, SLAPI_REQUESTOR_DN, &clientDn );
+ if (acl_anom_profile->anom_numacls &&
+ acl_anom_profile->anom_signature &&
+ (( NULL == clientDn) || (clientDn && *clientDn == '\0')) )
+ return 1;
+
+ return 0;
+}
+
diff --git a/ldap/servers/plugins/acl/acldllmain.c b/ldap/servers/plugins/acl/acldllmain.c
new file mode 100644
index 00000000..21ab60c4
--- /dev/null
+++ b/ldap/servers/plugins/acl/acldllmain.c
@@ -0,0 +1,128 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include "acl.h"
+
+#ifdef _WIN32
+/* Lifted from Q125688
+ * How to Port a 16-bit DLL to a Win32 DLL
+ * on the MSVC 4.0 CD
+ */
+BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved)
+{
+ WSADATA wsadata;
+
+ switch (fdwReason)
+ {
+ case DLL_PROCESS_ATTACH:
+ /* Code from LibMain inserted here. Return TRUE to keep the
+ DLL loaded or return FALSE to fail loading the DLL.
+
+ You may have to modify the code in your original LibMain to
+ account for the fact that it may be called more than once.
+ You will get one DLL_PROCESS_ATTACH for each process that
+ loads the DLL. This is different from LibMain which gets
+ called only once when the DLL is loaded. The only time this
+ is critical is when you are using shared data sections.
+ If you are using shared data sections for statically
+ allocated data, you will need to be careful to initialize it
+ only once. Check your code carefully.
+
+ Certain one-time initializations may now need to be done for
+ each process that attaches. You may also not need code from
+ your original LibMain because the operating system may now
+ be doing it for you.
+ */
+ /*
+ * 16 bit code calls UnlockData()
+ * which is mapped to UnlockSegment in windows.h
+ * in 32 bit world UnlockData is not defined anywhere
+ * UnlockSegment is mapped to GlobalUnfix in winbase.h
+ * and the docs for both UnlockSegment and GlobalUnfix say
+ * ".. function is oboslete. Segments have no meaning
+ * in the 32-bit environment". So we do nothing here.
+ */
+
+ if( errno = WSAStartup(0x0101, &wsadata ) != 0 )
+ return FALSE;
+
+ break;
+
+ case DLL_THREAD_ATTACH:
+ /* Called each time a thread is created in a process that has
+ already loaded (attached to) this DLL. Does not get called
+ for each thread that exists in the process before it loaded
+ the DLL.
+
+ Do thread-specific initialization here.
+ */
+ break;
+
+ case DLL_THREAD_DETACH:
+ /* Same as above, but called when a thread in the process
+ exits.
+
+ Do thread-specific cleanup here.
+ */
+ break;
+
+ case DLL_PROCESS_DETACH:
+ /* Code from _WEP inserted here. This code may (like the
+ LibMain) not be necessary. Check to make certain that the
+ operating system is not doing it for you.
+ */
+ WSACleanup();
+
+ break;
+ }
+ /* The return value is only used for DLL_PROCESS_ATTACH; all other
+ conditions are ignored. */
+ return TRUE; // successful DLL_PROCESS_ATTACH
+}
+#else
+int CALLBACK
+LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine )
+{
+ /*UnlockData( 0 );*/
+ return( 1 );
+}
+#endif
+
+#ifdef LDAP_DEBUG
+#ifndef _WIN32
+#include <stdarg.h>
+#include <stdio.h>
+
+void LDAPDebug( int level, char* fmt, ... )
+{
+ static char debugBuf[1024];
+
+ if (module_ldap_debug && (*module_ldap_debug & level))
+ {
+ va_list ap;
+ va_start (ap, fmt);
+ _snprintf (debugBuf, sizeof(debugBuf), fmt, ap);
+ va_end (ap);
+
+ OutputDebugString (debugBuf);
+ }
+}
+#endif
+#endif
+
+#ifndef _WIN32
+
+/* The 16-bit version of the RTL does not implement perror() */
+
+#include <stdio.h>
+
+void perror( const char *msg )
+{
+ char buf[128];
+ wsprintf( buf, "%s: error %d\n", msg, WSAGetLastError()) ;
+ OutputDebugString( buf );
+}
+
+#endif
diff --git a/ldap/servers/plugins/acl/acleffectiverights.c b/ldap/servers/plugins/acl/acleffectiverights.c
new file mode 100644
index 00000000..1e250e96
--- /dev/null
+++ b/ldap/servers/plugins/acl/acleffectiverights.c
@@ -0,0 +1,674 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2004 Netscape Communications Corporation
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#include "acl.h"
+
+static int
+_ger_g_permission_granted ( Slapi_PBlock *pb, Slapi_Entry *e, char **errbuf )
+{
+ char *proxydn = NULL;
+ Slapi_DN *requestor_sdn, *entry_sdn;
+ char *errtext = NULL;
+ int isroot;
+ int rc;
+
+ /*
+ * Theorically, we should check if the entry has "g"
+ * permission granted to the requestor. If granted,
+ * allows the effective rights on that entry and its
+ * attributes within the entry to be returned for
+ * ANY subject.
+ *
+ * "G" permission granting has not been implemented yet,
+ * the current release assumes that "g" permission be
+ * granted to root and owner of any entry.
+ */
+
+ /*
+ * The requestor may be either the bind dn or a proxy dn
+ */
+ acl_get_proxyauth_dn ( pb, &proxydn, &errtext );
+ if ( proxydn != NULL )
+ {
+ requestor_sdn = slapi_sdn_new_dn_passin ( proxydn );
+ }
+ else
+ {
+ requestor_sdn = &(pb->pb_op->o_sdn);
+ }
+ if ( slapi_sdn_get_dn (requestor_sdn) == NULL )
+ {
+ slapi_log_error (SLAPI_LOG_ACL, plugin_name,
+ "_ger_g_permission_granted: anonymous has no g permission\n" );
+ rc = LDAP_INSUFFICIENT_ACCESS;
+ goto bailout;
+ }
+ isroot = slapi_dn_isroot ( slapi_sdn_get_dn (requestor_sdn) );
+ if ( isroot )
+ {
+ /* Root has "g" permission on any entry */
+ rc = LDAP_SUCCESS;
+ goto bailout;
+ }
+
+ entry_sdn = slapi_entry_get_sdn ( e );
+ if ( entry_sdn == NULL || slapi_sdn_get_dn (entry_sdn) == NULL )
+ {
+ rc = LDAP_SUCCESS;
+ goto bailout;
+ }
+
+ if ( slapi_sdn_compare ( requestor_sdn, entry_sdn ) == 0 )
+ {
+ /* Owner has "g" permission on his own entry */
+ rc = LDAP_SUCCESS;
+ goto bailout;
+ }
+
+ aclutil_str_appened ( errbuf, "get-effective-rights: requestor has no g permission on the entry" );
+ slapi_log_error (SLAPI_LOG_ACL, plugin_name,
+ "_ger_g_permission_granted: %s\n", *errbuf);
+ rc = LDAP_INSUFFICIENT_ACCESS;
+
+bailout:
+ if ( proxydn )
+ {
+ /* The ownership of proxydn has passed to requestor_sdn */
+ slapi_sdn_free ( &requestor_sdn );
+ }
+ return rc;
+}
+
+static int
+_ger_parse_control ( Slapi_PBlock *pb, char **subjectndn, int *iscritical, char **errbuf )
+{
+ LDAPControl **requestcontrols;
+ struct berval *subjectber;
+ BerElement *ber;
+
+ if (NULL == subjectndn)
+ {
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+ *subjectndn = NULL;
+
+ /*
+ * Get the control
+ */
+ slapi_pblock_get ( pb, SLAPI_REQCONTROLS, (void *) &requestcontrols );
+ slapi_control_present ( requestcontrols,
+ LDAP_CONTROL_GET_EFFECTIVE_RIGHTS,
+ &subjectber,
+ iscritical );
+ if ( subjectber == NULL || subjectber->bv_val == NULL ||
+ subjectber->bv_len == 0 )
+ {
+ aclutil_str_appened ( errbuf, "get-effective-rights: missing subject" );
+ slapi_log_error (SLAPI_LOG_FATAL, plugin_name, "%s\n", *errbuf );
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ if ( strncasecmp ( "dn:", subjectber->bv_val, 3 ) == 0 )
+ {
+ /*
+ * This is a non-standard support to allow the subject being a plain
+ * or base64 encoding string. Hence users using -J option in
+ * ldapsearch don't have to do BER encoding for the subject.
+ */
+ *subjectndn = slapi_ch_malloc ( subjectber->bv_len + 1 );
+ strncpy ( *subjectndn, subjectber->bv_val, subjectber->bv_len );
+ *(*subjectndn + subjectber->bv_len) = '\0';
+ }
+ else
+ {
+ ber = ber_init (subjectber);
+ if ( ber == NULL )
+ {
+ aclutil_str_appened ( errbuf, "get-effective-rights: ber_init failed for the subject" );
+ slapi_log_error (SLAPI_LOG_FATAL, plugin_name, "%s\n", *errbuf );
+ return LDAP_OPERATIONS_ERROR;
+ }
+ /* "a" means to allocate storage as needed for octet string */
+ if ( ber_scanf (ber, "a", subjectndn) == LBER_ERROR )
+ {
+ aclutil_str_appened ( errbuf, "get-effective-rights: invalid ber tag in the subject" );
+ slapi_log_error (SLAPI_LOG_FATAL, plugin_name, "%s\n", *errbuf );
+ ber_free ( ber, 1 );
+ return LDAP_INVALID_SYNTAX;
+ }
+ ber_free ( ber, 1 );
+ }
+
+ /*
+ * The current implementation limits the subject to authorization ID
+ * (see section 9 of RFC 2829) only. It also only supports the "dnAuthzId"
+ * flavor, which looks like "dn:<DN>" where null <DN> is for anonymous.
+ */
+ if ( NULL == *subjectndn || strlen (*subjectndn) < 3 ||
+ strncasecmp ( "dn:", *subjectndn, 3 ) != 0 )
+ {
+ aclutil_str_appened ( errbuf, "get-effective-rights: subject is not dnAuthzId" );
+ slapi_log_error (SLAPI_LOG_FATAL, plugin_name, "%s\n", *errbuf );
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ strcpy ( *subjectndn, *subjectndn + 3 );
+ slapi_dn_normalize ( *subjectndn );
+ return LDAP_SUCCESS;
+}
+
+static void
+_ger_release_gerpb (
+ Slapi_PBlock **gerpb,
+ void **aclcb, /* original aclcb */
+ Slapi_PBlock *pb /* original pb */
+ )
+{
+ if ( *gerpb )
+ {
+ /* Return conn to pb */
+ slapi_pblock_set ( *gerpb, SLAPI_CONNECTION, NULL );
+ slapi_pblock_destroy ( *gerpb );
+ *gerpb = NULL;
+ }
+
+ /* Put the original aclcb back to pb */
+ if ( *aclcb )
+ {
+ Connection *conn = NULL;
+ slapi_pblock_get ( pb, SLAPI_CONNECTION, &conn );
+ if (conn)
+ {
+ struct aclcb *geraclcb;
+ geraclcb = (struct aclcb *) acl_get_ext ( ACL_EXT_CONNECTION, conn );
+ acl_conn_ext_destructor ( geraclcb, NULL, NULL );
+ acl_set_ext ( ACL_EXT_CONNECTION, conn, *aclcb );
+ *aclcb = NULL;
+ }
+ }
+}
+
+static int
+_ger_new_gerpb (
+ Slapi_PBlock *pb,
+ Slapi_Entry *e,
+ const char *subjectndn,
+ Slapi_PBlock **gerpb,
+ void **aclcb, /* original aclcb */
+ char **errbuf
+ )
+{
+ Connection *conn;
+ struct acl_cblock *geraclcb;
+ Acl_PBlock *aclpb, *geraclpb;
+ Operation *op, *gerop;
+ int rc = LDAP_SUCCESS;
+
+ *aclcb = NULL;
+ *gerpb = slapi_pblock_new ();
+ if ( *gerpb == NULL )
+ {
+ rc = LDAP_NO_MEMORY;
+ goto bailout;
+ }
+
+ {
+ /* aclpb initialization needs the backend */
+ Slapi_Backend *be;
+ slapi_pblock_get ( pb, SLAPI_BACKEND, &be );
+ slapi_pblock_set ( *gerpb, SLAPI_BACKEND, be );
+ }
+
+ {
+ int isroot = slapi_dn_isroot ( subjectndn );
+ slapi_pblock_set ( *gerpb, SLAPI_REQUESTOR_ISROOT, &isroot );
+ }
+
+ /* Save requestor's aclcb and set subjectdn's one */
+ {
+ slapi_pblock_get ( pb, SLAPI_CONNECTION, &conn );
+ slapi_pblock_set ( *gerpb, SLAPI_CONNECTION, conn );
+
+ /* Can't share the conn->aclcb because of different context */
+ geraclcb = (struct acl_cblock *) acl_conn_ext_constructor ( NULL, NULL);
+ if ( geraclcb == NULL )
+ {
+ rc = LDAP_NO_MEMORY;
+ goto bailout;
+ }
+ slapi_sdn_set_ndn_byval ( geraclcb->aclcb_sdn, subjectndn );
+ *aclcb = acl_get_ext ( ACL_EXT_CONNECTION, conn );
+ acl_set_ext ( ACL_EXT_CONNECTION, conn, (void *) geraclcb );
+ }
+
+ {
+ gerop = operation_new ( OP_FLAG_INTERNAL );
+ if ( gerop == NULL )
+ {
+ rc = LDAP_NO_MEMORY;
+ goto bailout;
+ }
+ /*
+ * conn is a no-use parameter in the functions
+ * chained down from factory_create_extension
+ */
+ gerop->o_extension = factory_create_extension ( get_operation_object_type(), (void *)gerop, (void *)conn );
+ slapi_pblock_set ( *gerpb, SLAPI_OPERATION, gerop );
+ slapi_sdn_set_dn_byval ( &gerop->o_sdn, subjectndn );
+ geraclpb = acl_get_ext ( ACL_EXT_OPERATION, (void *)gerop);
+ acl_init_aclpb ( *gerpb, geraclpb, subjectndn, 0 );
+ geraclpb->aclpb_res_type |= ACLPB_EFFECTIVE_RIGHTS;
+ }
+
+
+bailout:
+ if ( rc != LDAP_SUCCESS )
+ {
+ _ger_release_gerpb ( gerpb, aclcb, pb );
+ }
+
+ return rc;
+}
+
+/*
+ * Callers should have already allocated *gerstr to hold at least
+ * "entryLevelRights: adnvxxx\n".
+ */
+unsigned long
+_ger_get_entry_rights (
+ Slapi_PBlock *gerpb,
+ Slapi_Entry *e,
+ const char *subjectndn,
+ char *gerstr,
+ char **errbuf
+ )
+{
+ unsigned long entryrights = 0;
+ Slapi_RDN *rdn = NULL;
+ const char *rdnstr = NULL;
+ char *equalsign = NULL;
+ char *rdntype = NULL;
+
+ strcpy ( gerstr, "entryLevelRights: " );
+
+ slapi_log_error (SLAPI_LOG_ACL, plugin_name,
+ "_ger_get_entry_rights: SLAPI_ACL_READ\n" );
+ if (acl_access_allowed(gerpb, e, "*", NULL, SLAPI_ACL_READ) == LDAP_SUCCESS)
+ {
+ /* v - view e */
+ entryrights |= SLAPI_ACL_READ;
+ strcat (gerstr, "v");
+ }
+ slapi_log_error (SLAPI_LOG_ACL, plugin_name,
+ "_ger_get_entry_rights: SLAPI_ACL_ADD\n" );
+ if (acl_access_allowed(gerpb, e, NULL, NULL, SLAPI_ACL_ADD) == LDAP_SUCCESS)
+ {
+ /* a - add child entry below e */
+ entryrights |= SLAPI_ACL_ADD;
+ strcat (gerstr, "a");
+ }
+ slapi_log_error (SLAPI_LOG_ACL, plugin_name,
+ "_ger_get_entry_rights: SLAPI_ACL_DELETE\n" );
+ if (acl_access_allowed(gerpb, e, NULL, NULL, SLAPI_ACL_DELETE) == LDAP_SUCCESS)
+ {
+ /* d - delete e */
+ entryrights |= SLAPI_ACL_DELETE;
+ strcat (gerstr, "d");
+ }
+ /*
+ * Some limitation/simplification applied here:
+ * - The modrdn right requires the rights to delete the old rdn and
+ * the new one. However we have no knowledge of what the new rdn
+ * is going to be.
+ * - In multi-valued RDN case, we check the right on
+ * the first rdn type only for now.
+ */
+ rdn = slapi_rdn_new_dn ( slapi_entry_get_ndn (e) );
+ rdnstr = slapi_rdn_get_rdn ( rdn );
+ if ( NULL != (equalsign = strchr ( rdnstr, '=' )) )
+ {
+ rdntype = slapi_ch_malloc ( equalsign-rdnstr+1 );
+ strncpy ( rdntype, rdnstr, equalsign-rdnstr );
+ rdntype [ equalsign-rdnstr ] = '\0';
+ slapi_log_error (SLAPI_LOG_ACL, plugin_name,
+ "_ger_get_entry_rights: SLAPI_ACL_WRITE_DEL & _ADD %s\n", rdntype );
+ if (acl_access_allowed(gerpb, e, rdntype, NULL,
+ ACLPB_SLAPI_ACL_WRITE_DEL) == LDAP_SUCCESS &&
+ acl_access_allowed(gerpb, e, rdntype, NULL,
+ ACLPB_SLAPI_ACL_WRITE_ADD) == LDAP_SUCCESS)
+ {
+ /* n - rename e */
+ entryrights |= SLAPI_ACL_WRITE;
+ strcat (gerstr, "n");
+ }
+ slapi_ch_free ( (void**) &rdntype );
+ }
+ slapi_rdn_free ( &rdn );
+
+done:
+ if ( entryrights == 0 )
+ {
+ strcat (gerstr, "none");
+ }
+
+ strcat (gerstr, "\n");
+
+ return entryrights;
+}
+
+/*
+ * *gerstr should point to a heap buffer since it may need
+ * to expand dynamically.
+ */
+unsigned long
+_ger_get_attr_rights (
+ Slapi_PBlock *gerpb,
+ Slapi_Entry *e,
+ const char *subjectndn,
+ char *type,
+ char **gerstr,
+ int *gerstrsize,
+ int isfirstattr,
+ char **errbuf
+ )
+{
+ unsigned long attrrights = 0;
+
+ /* Enough space for " $type:rwoscxx" ? */
+ if ( (*gerstrsize - strlen(*gerstr)) < (strlen(type) + 16) )
+ {
+ /* slapi_ch_realloc() exits if realloc() failed */
+ *gerstrsize += 256;
+ *gerstr = slapi_ch_realloc ( *gerstr, *gerstrsize );
+ }
+ if (!isfirstattr)
+ {
+ strcat ( *gerstr, ", " );
+ }
+ sprintf ( *gerstr + strlen(*gerstr), "%s:", type );
+
+ slapi_log_error (SLAPI_LOG_ACL, plugin_name,
+ "_ger_get_attr_rights: SLAPI_ACL_READ %s\n", type );
+ if (acl_access_allowed(gerpb, e, type, NULL, SLAPI_ACL_READ) == LDAP_SUCCESS)
+ {
+ /* r - read the values of type */
+ attrrights |= SLAPI_ACL_READ;
+ strcat (*gerstr, "r");
+ }
+ slapi_log_error (SLAPI_LOG_ACL, plugin_name,
+ "_ger_get_attr_rights: SLAPI_ACL_SEARCH %s\n", type );
+ if (acl_access_allowed(gerpb, e, type, NULL, SLAPI_ACL_SEARCH) == LDAP_SUCCESS)
+ {
+ /* s - search the values of type */
+ attrrights |= SLAPI_ACL_SEARCH;
+ strcat (*gerstr, "s");
+ }
+ slapi_log_error (SLAPI_LOG_ACL, plugin_name,
+ "_ger_get_attr_rights: SLAPI_ACL_COMPARE %s\n", type );
+ if (acl_access_allowed(gerpb, e, type, NULL, SLAPI_ACL_COMPARE) == LDAP_SUCCESS)
+ {
+ /* c - compare the values of type */
+ attrrights |= SLAPI_ACL_COMPARE;
+ strcat (*gerstr, "c");
+ }
+ slapi_log_error (SLAPI_LOG_ACL, plugin_name,
+ "_ger_get_attr_rights: SLAPI_ACL_WRITE_ADD %s\n", type );
+ if (acl_access_allowed(gerpb, e, type, NULL, ACLPB_SLAPI_ACL_WRITE_ADD) == LDAP_SUCCESS)
+ {
+ /* w - add the values of type */
+ attrrights |= ACLPB_SLAPI_ACL_WRITE_ADD;
+ strcat (*gerstr, "w");
+ }
+ slapi_log_error (SLAPI_LOG_ACL, plugin_name,
+ "_ger_get_attr_rights: SLAPI_ACL_WRITE_DEL %s\n", type );
+ if (acl_access_allowed(gerpb, e, type, NULL, ACLPB_SLAPI_ACL_WRITE_DEL) == LDAP_SUCCESS)
+ {
+ /* o - delete the values of type */
+ attrrights |= ACLPB_SLAPI_ACL_WRITE_DEL;
+ strcat (*gerstr, "o");
+ }
+ /* If subjectdn has no general write right, check for self write */
+ if ( 0 == (attrrights & (ACLPB_SLAPI_ACL_WRITE_DEL | ACLPB_SLAPI_ACL_WRITE_ADD)) )
+ {
+ struct berval val;
+
+ val.bv_val = (char *)subjectndn;
+ val.bv_len = strlen (subjectndn);
+
+ if (acl_access_allowed(gerpb, e, type, &val, ACLPB_SLAPI_ACL_WRITE_ADD) == LDAP_SUCCESS)
+ {
+ /* W - add self to the attribute */
+ attrrights |= ACLPB_SLAPI_ACL_WRITE_ADD;
+ strcat (*gerstr, "W");
+ }
+ if (acl_access_allowed(gerpb, e, type, &val, ACLPB_SLAPI_ACL_WRITE_DEL) == LDAP_SUCCESS)
+ {
+ /* O - delete self from the attribute */
+ attrrights |= ACLPB_SLAPI_ACL_WRITE_DEL;
+ strcat (*gerstr, "O");
+ }
+ }
+
+ if ( attrrights == 0 )
+ {
+ strcat (*gerstr, "none");
+ }
+
+ return attrrights;
+}
+
+void
+_ger_get_attrs_rights (
+ Slapi_PBlock *gerpb,
+ Slapi_Entry *e,
+ const char *subjectndn,
+ char **attrs,
+ char **gerstr,
+ int *gerstrsize,
+ char **errbuf
+ )
+{
+ int isfirstattr = 1;
+
+ /* gerstr was initially allocated with enough space for one more line */
+ strcat ( *gerstr, "attributeLevelRights: " );
+
+ if (attrs && *attrs)
+ {
+ int i;
+ for ( i = 0; attrs[i]; i++ )
+ {
+ _ger_get_attr_rights ( gerpb, e, subjectndn, attrs[i], gerstr, gerstrsize, isfirstattr, errbuf );
+ isfirstattr = 0;
+ }
+ }
+ else
+ {
+ Slapi_Attr *prevattr = NULL, *attr;
+ char *type;
+
+ while ( slapi_entry_next_attr ( e, prevattr, &attr ) == 0 )
+ {
+ if ( ! slapi_attr_flag_is_set (attr, SLAPI_ATTR_FLAG_OPATTR) )
+ {
+ slapi_attr_get_type ( attr, &type );
+ _ger_get_attr_rights ( gerpb, e, subjectndn, type, gerstr, gerstrsize, isfirstattr, errbuf );
+ isfirstattr = 0;
+ }
+ prevattr = attr;
+ }
+ }
+
+ if ( isfirstattr )
+ {
+ /* not a single attribute was retrived or specified */
+ strcat ( *gerstr, "*:none" );
+ }
+ return;
+}
+
+/*
+ * controlType = LDAP_CONTROL_GET_EFFECTIVE_RIGHTS;
+ * criticality = n/a;
+ * controlValue = OCTET STRING of BER encoding of the SEQUENCE of
+ * ENUMERATED LDAP code
+ */
+void
+_ger_set_response_control (
+ Slapi_PBlock *pb,
+ int iscritical,
+ int rc
+ )
+{
+ LDAPControl **resultctrls = NULL;
+ LDAPControl gerrespctrl;
+ BerElement *ber = NULL;
+ struct berval *berval = NULL;
+ int found = 0;
+ int i;
+
+ if ( (ber = der_alloc ()) == NULL )
+ {
+ goto bailout;
+ }
+
+ /* begin sequence, enumeration, end sequence */
+ ber_printf ( ber, "{e}", rc );
+ if ( ber_flatten ( ber, &berval ) != LDAP_SUCCESS )
+ {
+ goto bailout;
+ }
+ gerrespctrl.ldctl_oid = LDAP_CONTROL_GET_EFFECTIVE_RIGHTS;
+ gerrespctrl.ldctl_iscritical = iscritical;
+ gerrespctrl.ldctl_value.bv_val = berval->bv_val;
+ gerrespctrl.ldctl_value.bv_len = berval->bv_len;
+
+ slapi_pblock_get ( pb, SLAPI_RESCONTROLS, &resultctrls );
+ for (i = 0; resultctrls && resultctrls[i]; i++)
+ {
+ if (strcmp(resultctrls[i]->ldctl_oid, LDAP_CONTROL_GET_EFFECTIVE_RIGHTS) == 0)
+ {
+ /*
+ * We get here if search returns more than one entry
+ * and this is not the first entry.
+ */
+ ldap_control_free ( resultctrls[i] );
+ resultctrls[i] = slapi_dup_control (&gerrespctrl);
+ found = 1;
+ break;
+ }
+ }
+
+ if ( !found )
+ {
+ /* slapi_pblock_set() will dup the control */
+ slapi_pblock_set ( pb, SLAPI_ADD_RESCONTROL, &gerrespctrl );
+ }
+
+bailout:
+ ber_free ( ber, 1 ); /* ber_free() checks for NULL param */
+ ber_bvfree ( berval ); /* ber_bvfree() checks for NULL param */
+}
+
+int
+acl_get_effective_rights (
+ Slapi_PBlock *pb,
+ Slapi_Entry *e, /* target entry */
+ char **attrs, /* Attribute of the entry */
+ struct berval *val, /* value of attr. NOT USED */
+ int access, /* requested access rights */
+ char **errbuf
+ )
+{
+ Slapi_PBlock *gerpb = NULL;
+ void *aclcb = NULL;
+ char *subjectndn = NULL;
+ char *gerstr = NULL;
+ int gerstrsize = 1024;
+ unsigned long entryrights;
+ int iscritical = 1;
+ int rc;
+
+ *errbuf = '\0';
+ gerstr = slapi_ch_malloc ( gerstrsize );
+
+ /*
+ * Get the subject
+ */
+ rc = _ger_parse_control (pb, &subjectndn, &iscritical, errbuf );
+ if ( rc != LDAP_SUCCESS )
+ {
+ goto bailout;
+ }
+
+ /*
+ * The requestor should have g permission on the entry
+ * to get the effective rights.
+ */
+ rc = _ger_g_permission_granted (pb, e, errbuf);
+ if ( rc != LDAP_SUCCESS )
+ {
+ goto bailout;
+ }
+
+ /*
+ * Construct a new pb
+ */
+ rc = _ger_new_gerpb ( pb, e, subjectndn, &gerpb, &aclcb, errbuf );
+ if ( rc != LDAP_SUCCESS )
+ {
+ goto bailout;
+ }
+
+ /* Get entry level effective rights */
+ entryrights = _ger_get_entry_rights ( gerpb, e, subjectndn, gerstr, errbuf );
+
+ /*
+ * Attribute level effective rights may not be NULL
+ * even if entry level's is.
+ */
+ _ger_get_attrs_rights ( gerpb, e, subjectndn, attrs, &gerstr, &gerstrsize, errbuf );
+
+bailout:
+ /*
+ * Now construct the response control
+ */
+ _ger_set_response_control ( pb, iscritical, rc );
+
+ if ( rc != LDAP_SUCCESS )
+ {
+ sprintf ( gerstr, "entryLevelRights: %d\nattributeLevelRights: *:%d", rc, rc );
+ }
+
+ slapi_log_error (SLAPI_LOG_ACLSUMMARY, plugin_name,
+ "###### Effective Rights on Entry (%s) for Subject (%s) ######\n",
+ slapi_entry_get_ndn (e), subjectndn);
+ slapi_log_error (SLAPI_LOG_ACLSUMMARY, plugin_name, "%s\n", gerstr);
+
+ /* Restore pb */
+ _ger_release_gerpb ( &gerpb, &aclcb, pb );
+
+ /*
+ * General plugin uses SLAPI_RESULT_TEXT for error text. Here
+ * SLAPI_PB_RESULT_TEXT is exclusively shared with add, dse and schema.
+ * slapi_pblock_set() will free any previous data, and
+ * pblock_done() will free SLAPI_PB_RESULT_TEXT.
+ */
+ slapi_pblock_set (pb, SLAPI_PB_RESULT_TEXT, gerstr);
+
+ if ( !iscritical )
+ {
+ /*
+ * If return code is not LDAP_SUCCESS, the server would
+ * abort sending the data of the entry to the client.
+ */
+ rc = LDAP_SUCCESS;
+ }
+
+ slapi_ch_free ( (void **) &subjectndn );
+ slapi_ch_free ( (void **) &gerstr );
+ return rc;
+}
diff --git a/ldap/servers/plugins/acl/aclgroup.c b/ldap/servers/plugins/acl/aclgroup.c
new file mode 100644
index 00000000..4cd7039f
--- /dev/null
+++ b/ldap/servers/plugins/acl/aclgroup.c
@@ -0,0 +1,442 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include "acl.h"
+
+/***************************************************************************
+ *
+ * This module deals with the global user group cache.
+ *
+ * A LRU queue mechanism is used to maintain the groups the user currently in.
+ * At this moment the QUEUE is invalidated if there is a group change. A better
+ * way would have been to invalidate only the one which are effected.
+ * However to accomplish that will require quite a bit of work which may not be
+ * cost-efftive.
+ **************************************************************************/
+static aclGroupCache *aclUserGroups;
+#define ACL_MAXCACHE_USERGROUPS 200
+
+#define ACLG_LOCK_GROUPCACHE_READ() PR_RWLock_Rlock ( aclUserGroups->aclg_rwlock )
+#define ACLG_LOCK_GROUPCACHE_WRITE() PR_RWLock_Wlock ( aclUserGroups->aclg_rwlock )
+#define ACLG_ULOCK_GROUPCACHE_WRITE() PR_RWLock_Unlock ( aclUserGroups->aclg_rwlock )
+#define ACLG_ULOCK_GROUPCACHE_READ() PR_RWLock_Unlock ( aclUserGroups->aclg_rwlock )
+
+
+static void __aclg__delete_userGroup ( aclUserGroup *u_group );
+
+
+int
+aclgroup_init ()
+{
+
+ aclUserGroups = ( aclGroupCache * ) slapi_ch_calloc (1, sizeof ( aclGroupCache ) );
+ if ( NULL == (aclUserGroups->aclg_rwlock = PR_NewRWLock( PR_RWLOCK_RANK_NONE,"Group LOCK"))) {
+ slapi_log_error(SLAPI_LOG_FATAL, plugin_name, "Unable to allocate RWLOCK for group cache\n");
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * aclg_init_userGroup
+ *
+ * Go thru the Global Group CACHE and see if we have group information for
+ * the user. The user's group cache is invalidated when a group is modified
+ * (in which case ALL usergroups are invalidated) or when the user's entry
+ * is modified in which case just his is invalidated.
+ *
+ * We need to scan the whole cache looking for a valid entry that matches
+ * this user. If we find invalid entries along the way.
+ *
+ * If we don't have anything it's fine. we will allocate a space when we
+ * need it i.e during the group evaluation.
+ *
+ * Inputs:
+ * struct acl_pblock - ACL private block
+ * char *dn - the client's dn
+ * int got_lock - 1: already obtained WRITE Lock
+ * - 0: Nope; get one
+ * Returns:
+ * None.
+ */
+
+void
+aclg_init_userGroup ( struct acl_pblock *aclpb, const char *n_dn , int got_lock )
+{
+ aclUserGroup *u_group = NULL;
+ aclUserGroup *next_ugroup = NULL;
+ aclUserGroup *p_group, *n_group;
+ int found = 0;
+
+ /* Check for Anonymous user */
+ if ( n_dn && *n_dn == '\0') return;
+
+ if ( !got_lock ) ACLG_LOCK_GROUPCACHE_WRITE ();
+ u_group = aclUserGroups->aclg_first;
+ aclpb->aclpb_groupinfo = NULL;
+
+ while ( u_group != NULL ) {
+ next_ugroup = u_group->aclug_next;
+ if ( aclUserGroups->aclg_signature != u_group->aclug_signature) {
+ /*
+ * This means that this usergroup is no longer valid and
+ * this operation so delete this one if no one is using it.
+ */
+
+ if ( !u_group->aclug_refcnt ) {
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "In traversal group deallocation\n", 0,0,0 );
+ __aclg__delete_userGroup (u_group);
+ }
+ } else {
+
+ /*
+ * Here, u_group is valid--if it matches then take it.
+ */
+ if ( slapi_utf8casecmp((ACLUCHP)u_group->aclug_ndn,
+ (ACLUCHP)n_dn ) == 0 ) {
+ u_group->aclug_refcnt++;
+ aclpb->aclpb_groupinfo = u_group;
+ found = 1;
+ break;
+ }
+ }
+ u_group = next_ugroup;
+ }
+
+ /* Move the new one to the top of the queue */
+ if ( found ) {
+ p_group = u_group->aclug_prev;
+ n_group = u_group->aclug_next;
+
+ if ( p_group ) {
+ aclUserGroup *t_group = NULL;
+
+ p_group->aclug_next = n_group;
+ if ( n_group ) n_group->aclug_prev = p_group;
+
+ t_group = aclUserGroups->aclg_first;
+ if ( t_group ) t_group->aclug_prev = u_group;
+
+ u_group->aclug_prev = NULL;
+ u_group->aclug_next = t_group;
+ aclUserGroups->aclg_first = u_group;
+
+ if ( u_group == aclUserGroups->aclg_last )
+ aclUserGroups->aclg_last = p_group;
+ }
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name, "acl_init_userGroup: found in cache for dn:%s\n", n_dn,0,0);
+ }
+ if (!got_lock ) ACLG_ULOCK_GROUPCACHE_WRITE ();
+}
+
+
+/*
+ *
+ * aclg_reset_userGroup
+ * Reset the reference count to the user's group.
+ *
+ * Inputs:
+ * struct acl_pblock -- The acl private block.
+ * Returns:
+ * None.
+ *
+ * Note: A WRITE Lock on the GroupCache is obtained during the change:
+ */
+void
+aclg_reset_userGroup ( struct acl_pblock *aclpb )
+{
+
+ aclUserGroup *u_group;
+
+ ACLG_LOCK_GROUPCACHE_WRITE();
+
+ if ( (u_group = aclpb->aclpb_groupinfo) != NULL ) {
+ u_group->aclug_refcnt--;
+
+ /* If I am the last one but I was using an invalid group cache
+ ** in the meantime, it is time now to get rid of it so that we will
+ ** not have duplicate cache.
+ */
+ if ( !u_group->aclug_refcnt &&
+ ( aclUserGroups->aclg_signature != u_group->aclug_signature )) {
+ __aclg__delete_userGroup ( u_group );
+ }
+ }
+ ACLG_ULOCK_GROUPCACHE_WRITE();
+ aclpb->aclpb_groupinfo = NULL;
+}
+
+/*
+ * Find a user group in the global cache, returning a pointer to it,
+ * ensuring that the refcnt has been bumped to stop
+ * another thread freeing it underneath us.
+*/
+
+aclUserGroup*
+aclg_find_userGroup(char *n_dn)
+{
+ aclUserGroup *u_group = NULL;
+ int i;
+
+ /* Check for Anonymous user */
+ if ( n_dn && *n_dn == '\0') return (NULL) ;
+
+ ACLG_LOCK_GROUPCACHE_READ ();
+ u_group = aclUserGroups->aclg_first;
+
+ for ( i=0; i < aclUserGroups->aclg_num_userGroups; i++ ) {
+ if ( aclUserGroups->aclg_signature == u_group->aclug_signature &&
+ slapi_utf8casecmp((ACLUCHP)u_group->aclug_ndn,
+ (ACLUCHP)n_dn ) == 0 ) {
+ aclg_reader_incr_ugroup_refcnt(u_group);
+ break;
+ }
+ u_group = u_group->aclug_next;
+ }
+
+ ACLG_ULOCK_GROUPCACHE_READ ();
+ return(u_group);
+}
+
+/*
+ * Mark a usergroup for removal from the usergroup cache.
+ * It will be removed by the first operation traversing the cache
+ * that finds it.
+*/
+void
+aclg_markUgroupForRemoval ( aclUserGroup* u_group) {
+
+ ACLG_LOCK_GROUPCACHE_WRITE ();
+ aclg_regen_ugroup_signature(u_group);
+ u_group->aclug_refcnt--;
+ ACLG_ULOCK_GROUPCACHE_WRITE ();
+}
+
+/*
+ *
+ * aclg_get_usersGroup
+ *
+ * If we already have a the group info then we are done. If we
+ * don't, then allocate a new one and attach it.
+ *
+ * Inputs:
+ * struct acl_pblock -- The acl private block.
+ * char *n_dn - normalized client's DN
+ *
+ * Returns:
+ * aclUserGroup - The Group info block.
+ *
+ */
+aclUserGroup *
+aclg_get_usersGroup ( struct acl_pblock *aclpb , char *n_dn)
+{
+
+ aclUserGroup *u_group, *f_group;
+
+ if ( aclpb && aclpb->aclpb_groupinfo )
+ return aclpb->aclpb_groupinfo;
+
+ ACLG_LOCK_GROUPCACHE_WRITE();
+
+ /* try it one more time. We might have one in the meantime */
+ aclg_init_userGroup (aclpb, n_dn , 1 /* got the lock */);
+ if ( aclpb && aclpb->aclpb_groupinfo ) {
+ ACLG_ULOCK_GROUPCACHE_WRITE();
+ return aclpb->aclpb_groupinfo;
+ }
+
+ /*
+ * It is possible at this point that we already have a group cache for the user
+ * but is is invalid. We can't use it anayway. So, we march along and allocate a new one.
+ * That's fine as the invalid one will be deallocated when done.
+ */
+
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name, "ALLOCATING GROUP FOR:%s\n", n_dn,0,0 );
+ u_group = ( aclUserGroup * ) slapi_ch_calloc ( 1, sizeof ( aclUserGroup ) );
+
+ u_group->aclug_refcnt = 1;
+ if ( (u_group->aclug_refcnt_mutex = PR_NewLock()) == NULL ) {
+ slapi_ch_free((void **)&u_group);
+ ACLG_ULOCK_GROUPCACHE_WRITE();
+ return(NULL);
+ }
+
+ u_group->aclug_member_groups = (char **)
+ slapi_ch_calloc ( 1,
+ (ACLUG_INCR_GROUPS_LIST * sizeof (char *)));
+ u_group->aclug_member_group_size = ACLUG_INCR_GROUPS_LIST;
+ u_group->aclug_numof_member_group = 0;
+
+ u_group->aclug_notmember_groups = (char **)
+ slapi_ch_calloc ( 1,
+ (ACLUG_INCR_GROUPS_LIST * sizeof (char *)));
+ u_group->aclug_notmember_group_size = ACLUG_INCR_GROUPS_LIST;
+ u_group->aclug_numof_notmember_group = 0;
+
+ u_group->aclug_ndn = slapi_ch_strdup ( n_dn ) ;
+
+ u_group->aclug_signature = aclUserGroups->aclg_signature;
+
+ /* Do we have alreday the max number. If we have then delete the last one */
+ if ( aclUserGroups->aclg_num_userGroups >= ACL_MAXCACHE_USERGROUPS - 5 ) {
+ aclUserGroup *d_group;
+
+ /* We need to traverse thru backwards and delete the one with a refcnt = 0 */
+ d_group = aclUserGroups->aclg_last;
+ while ( d_group ) {
+ if ( !d_group->aclug_refcnt ) {
+ __aclg__delete_userGroup ( d_group );
+ break;
+ } else {
+ d_group = d_group->aclug_prev;
+ }
+ }
+
+ /* If we didn't find any, which should be never,
+ ** we have 5 more tries to do it.
+ */
+ }
+ f_group = aclUserGroups->aclg_first;
+ u_group->aclug_next = f_group;
+ if ( f_group ) f_group->aclug_prev = u_group;
+
+ aclUserGroups->aclg_first = u_group;
+ if ( aclUserGroups->aclg_last == NULL )
+ aclUserGroups->aclg_last = u_group;
+
+ aclUserGroups->aclg_num_userGroups++;
+
+ /* Put it in the queue */
+ ACLG_ULOCK_GROUPCACHE_WRITE();
+
+ /* Now hang on to it */
+ aclpb->aclpb_groupinfo = u_group;
+ return u_group;
+}
+
+/*
+ *
+ * __aclg__delete_userGroup
+ *
+ * Delete the User's Group cache.
+ *
+ * Inputs:
+ * aclUserGroup - remove this one
+ * Returns:
+ * None.
+ *
+ * Note: A WRITE Lock on the GroupCache is obtained by the caller
+ */
+static void
+__aclg__delete_userGroup ( aclUserGroup *u_group )
+{
+
+ aclUserGroup *next_group, *prev_group;
+ int i;
+
+ if ( !u_group ) return;
+
+ prev_group = u_group->aclug_prev;
+ next_group = u_group->aclug_next;
+
+ /*
+ * At this point we must have a 0 refcnt or else we are in a bad shape.
+ * If we don't have one then at least remove the user's dn so that it will
+ * be in a condemned state and later deleted.
+ */
+
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name, "DEALLOCATING GROUP FOR:%s\n", u_group->aclug_ndn,0,0 );
+
+ slapi_ch_free ( (void **) &u_group->aclug_ndn );
+
+ PR_DestroyLock(u_group->aclug_refcnt_mutex);
+
+ /* Remove the member GROUPS */
+ for (i=0; i < u_group->aclug_numof_member_group; i++ )
+ slapi_ch_free ( (void **) &u_group->aclug_member_groups[i] );
+ slapi_ch_free ( (void **) &u_group->aclug_member_groups );
+
+ /* Remove the NOT member GROUPS */
+ for (i=0; i < u_group->aclug_numof_notmember_group; i++ )
+ slapi_ch_free ( (void **) &u_group->aclug_notmember_groups[i] );
+ slapi_ch_free ( (void **) &u_group->aclug_notmember_groups );
+
+ slapi_ch_free ( (void **) &u_group );
+
+ if ( prev_group == NULL && next_group == NULL ) {
+ aclUserGroups->aclg_first = NULL;
+ aclUserGroups->aclg_last = NULL;
+ } else if ( prev_group == NULL ) {
+ next_group->aclug_prev = NULL;
+ aclUserGroups->aclg_first = next_group;
+ } else {
+ prev_group->aclug_next = next_group;
+ if ( next_group )
+ next_group->aclug_prev = prev_group;
+ else
+ aclUserGroups->aclg_last = prev_group;
+ }
+ aclUserGroups->aclg_num_userGroups--;
+}
+
+void
+aclg_regen_group_signature( )
+{
+ aclUserGroups->aclg_signature = aclutil_gen_signature ( aclUserGroups->aclg_signature );
+}
+
+void
+aclg_regen_ugroup_signature( aclUserGroup *ugroup)
+{
+ ugroup->aclug_signature =
+ aclutil_gen_signature ( ugroup->aclug_signature );
+}
+
+void
+aclg_lock_groupCache ( int type /* 1 for reader and 2 for writer */)
+{
+
+ if (type == 1 )
+ ACLG_LOCK_GROUPCACHE_READ();
+ else
+ ACLG_LOCK_GROUPCACHE_WRITE();
+}
+
+void
+aclg_unlock_groupCache ( int type /* 1 for reader and 2 for writer */)
+{
+
+ if (type == 1 )
+ ACLG_ULOCK_GROUPCACHE_READ();
+ else
+ ACLG_ULOCK_GROUPCACHE_WRITE();
+}
+
+
+/*
+ * If you have the write lock on the group cache, you can
+ * increment the refcnt without taking the mutex.
+ * If you just have the reader lock on the refcnt then you need to
+ * take the mutex on the refcnt to increment it--which is what this routine is
+ * for.
+ *
+*/
+
+void
+aclg_reader_incr_ugroup_refcnt(aclUserGroup* u_group) {
+
+ PR_Lock(u_group->aclug_refcnt_mutex);
+ u_group->aclug_refcnt++;
+ PR_Unlock(u_group->aclug_refcnt_mutex);
+}
+
+/* You need the usergroups read lock to call this routine*/
+int
+aclg_numof_usergroups(void) {
+
+ return(aclUserGroups->aclg_num_userGroups);
+}
+
diff --git a/ldap/servers/plugins/acl/aclinit.c b/ldap/servers/plugins/acl/aclinit.c
new file mode 100644
index 00000000..21e54337
--- /dev/null
+++ b/ldap/servers/plugins/acl/aclinit.c
@@ -0,0 +1,537 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include "acl.h"
+
+static int __aclinit__RegisterLases(void);
+static int __aclinit__RegisterAttributes(void);
+static int __aclinit_handler(Slapi_Entry *e, void *callback_data);
+
+/***************************************************************************
+*
+* aclinit_main()
+* Main routine which is called at the server boot up time.
+*
+* 1) Reads all the ACI entries from the database and creates
+* the ACL list.
+* 2) Registers all the LASes and the GetAttrs supported by the DS.
+* 3) Generates anonymous profiles.
+* 4) Registers proxy control
+* 5) Creates aclpb pool
+*
+* Input:
+* None.
+*
+* Returns:
+* 0 -- no error
+* 1 -- Error
+*
+* Error Handling:
+* If any error found during the ACL generation, error is logged.
+*
+**************************************************************************/
+static int acl_initialized = 0;
+int
+aclinit_main()
+{
+ char *cookie = NULL;
+ Slapi_PBlock *pb;
+ int rv;
+ Slapi_DN *sdn;
+ void *node;
+
+ if (acl_initialized) {
+ /* There is no need to do anything more */
+ return 0;
+ }
+
+ /* Initialize the LIBACCESS ACL library */
+ if (ACL_Init() != 0) {
+ slapi_log_error(SLAPI_LOG_FATAL, plugin_name,
+ "ACL Library Initialization failed\n",0,0,0);
+ return 1;
+ }
+
+ /* register all the LASes supported by the DS */
+ if (ACL_ERR == __aclinit__RegisterLases()) {
+ /* Error is already logged */
+ return 1;
+ }
+
+ /* Register all the Attrs */
+ if (ACL_ERR == __aclinit__RegisterAttributes()) {
+ /* Error is already logged */
+ return 1;
+ }
+
+ /*
+ * Register to get backend state changes so we can add/remove
+ * acis from backends that come up and go down.
+ */
+
+ slapi_register_backend_state_change((void *) NULL, acl_be_state_change_fnc);
+
+
+ /* register the extensions */
+ /* ONREPL Moved to the acl_init function because extensions
+ need to be registered before any operations are issued
+ if ( 0 != acl_init_ext() ) {
+ slapi_log_error ( SLAPI_LOG_FATAL, plugin_name,
+ "Unable to initialize the extensions\n");
+ return 1;
+ } */
+
+ /* create the mutex array */
+ if ( 0 != aclext_alloc_lockarray ( ) ) {
+ slapi_log_error ( SLAPI_LOG_FATAL, plugin_name,
+ "Unable to create the mutext array\n");
+ return 1;
+ }
+
+ /* Allocate the pool */
+ if ( 0 != acl_create_aclpb_pool () ) {
+ slapi_log_error ( SLAPI_LOG_FATAL, plugin_name,
+ "Unable to create the acl private pool\n");
+ return 1;
+ }
+
+ /*
+ * Now read all the ACLs from all the backends and put it
+ * in a list
+ */
+ /* initialize the ACLLIST sub-system */
+ if ( 0 != (rv = acllist_init ( ))) {
+ slapi_log_error ( SLAPI_LOG_FATAL, plugin_name,
+ "Unable to initialize the plugin:%d\n", rv );
+ return 1;
+ }
+
+ /* Initialize the anonymous profile i.e., generate it */
+ rv = aclanom_init ();
+
+ pb = slapi_pblock_new();
+
+ /*
+ * search for the aci_attr_type attributes of all entries.
+ *
+ * slapi_get_fist_suffix() and slapi_get_next_suffix() do not return the
+ * rootdse entry so we search for acis in there explicitly here.
+ */
+
+ sdn = slapi_sdn_new_dn_byval("");
+ slapi_log_error ( SLAPI_LOG_ACL, plugin_name,
+ "Searching for all acis(scope base) at suffix ''\n");
+ aclinit_search_and_update_aci ( 0, /* thisbeonly */
+ sdn, /* base */
+ NULL, /* be name*/
+ LDAP_SCOPE_BASE, ACL_ADD_ACIS,
+ DO_TAKE_ACLCACHE_WRITELOCK);
+ slapi_sdn_free(&sdn);
+
+ sdn = slapi_get_first_suffix( &node, 1 );
+ while (sdn)
+ {
+ slapi_log_error ( SLAPI_LOG_ACL, plugin_name,
+ "Searching for all acis(scope subtree) at suffix '%s'\n",
+ slapi_sdn_get_dn(sdn) );
+ aclinit_search_and_update_aci ( 0, /* thisbeonly */
+ sdn, /* base */
+ NULL, /* be name*/
+ LDAP_SCOPE_SUBTREE, ACL_ADD_ACIS,
+ DO_TAKE_ACLCACHE_WRITELOCK);
+ sdn = slapi_get_next_suffix( &node, 1 );
+ }
+
+ /* Initialize it. */
+ acl_initialized = 1;
+
+ /* generate the signatures */
+ acl_set_aclsignature ( aclutil_gen_signature ( 100 ) );
+
+ /* Initialize the user-group cache */
+ rv = aclgroup_init ( );
+
+ aclanom_gen_anomProfile (DO_TAKE_ACLCACHE_READLOCK);
+
+ /* Register both of the proxied authorization controls (version 1 and 2) */
+ slapi_register_supported_control( LDAP_CONTROL_PROXYAUTH,
+ SLAPI_OPERATION_SEARCH | SLAPI_OPERATION_COMPARE
+ | SLAPI_OPERATION_ADD | SLAPI_OPERATION_DELETE
+ | SLAPI_OPERATION_MODIFY | SLAPI_OPERATION_MODDN
+ | SLAPI_OPERATION_EXTENDED );
+ slapi_register_supported_control( LDAP_CONTROL_PROXIEDAUTH,
+ SLAPI_OPERATION_SEARCH | SLAPI_OPERATION_COMPARE
+ | SLAPI_OPERATION_ADD | SLAPI_OPERATION_DELETE
+ | SLAPI_OPERATION_MODIFY | SLAPI_OPERATION_MODDN
+ | SLAPI_OPERATION_EXTENDED );
+
+ slapi_pblock_destroy ( pb );
+ return 0;
+}
+/*
+ * This routine is the one that scans for acis and either adds them
+ * to the internal cache (op==ACL_ADD_ACIS) or deletes them
+ * (op==ACL_REMOVE_ACIS).
+ *
+ * If thisbeonly is 0 the search
+ * is conducted on the base with the specifed scope and be_name is ignored.
+ * This is used at startup time where we iterate over all suffixes, searching
+ * for all the acis in the DIT to load the ACL cache.
+ *
+ * If thisbeonly is 1 then then a be_name must be specified.
+ * In this case we will search in that backend ONLY.
+ * This is used in the case where a backend is turned on and off--in this
+ * case we only want to add/remove the acis in that particular backend and
+ * not for example in any backends below that one.
+*/
+
+int
+aclinit_search_and_update_aci ( int thisbeonly, const Slapi_DN *base,
+ char *be_name, int scope, int op,
+ acl_lock_flag_t lock_flag )
+{
+ char *attrs[2] = { "aci", NULL };
+ /* Tell __aclinit_handler whether it's an add or a delete */
+ int any_error = op;
+ Slapi_PBlock *aPb;
+ LDAPControl **ctrls=NULL;
+ int retval;
+ struct berval *bval;
+ aclinit_handler_callback_data_t call_back_data;
+
+ PR_ASSERT( lock_flag == DONT_TAKE_ACLCACHE_WRITELOCK ||
+ lock_flag == DO_TAKE_ACLCACHE_WRITELOCK);
+
+ if ( thisbeonly && be_name == NULL) {
+ slapi_log_error ( SLAPI_LOG_FATAL, plugin_name,
+ "Error: This be_name must be specified.\n", 0, 0, 0);
+ return -1;
+ }
+
+
+ /*
+ * We need to explicitly request (objectclass=ldapsubentry)
+ * in order to get all the subentry acis too.
+ * Note that subentries can be added under subentries (although its not
+ * recommended) so that
+ * there may be non-trivial acis under a subentry.
+ */
+
+ /* Use new search internal API */
+ /* and never retrieve aci from a remote server */
+ aPb = slapi_pblock_new ();
+
+ /*
+ * Set up the control to say "Only get acis from this Backend--
+ * there may be more backends under this one.
+ */
+
+ if ( thisbeonly ) {
+
+ bval = (struct berval *)slapi_ch_malloc(sizeof(struct berval));
+ bval->bv_len = strlen(be_name) + 1;
+ bval->bv_val = slapi_ch_strdup(be_name);
+
+ ctrls = (LDAPControl **)slapi_ch_calloc( 2, sizeof(LDAPControl *));
+ ctrls[0] = NULL;
+ ctrls[1] = NULL;
+
+ retval = slapi_build_control_from_berval(
+ MTN_CONTROL_USE_ONE_BACKEND_OID,
+ bval,
+ 1 /* is critical */,
+ ctrls);
+
+ }
+
+ slapi_search_internal_set_pb ( aPb,
+ slapi_sdn_get_dn(base),
+ scope,
+ "(|(aci=*)(objectclass=ldapsubentry))",
+ attrs,
+ 0 /* attrsonly */,
+ ctrls /* controls: SLAPI_ARGCONTROLS */,
+ NULL /* uniqueid */,
+ aclplugin_get_identity (ACL_PLUGIN_IDENTITY),
+ SLAPI_OP_FLAG_NEVER_CHAIN /* actions : get local aci only */);
+
+ if (thisbeonly) {
+ slapi_pblock_set(aPb, SLAPI_REQCONTROLS, ctrls);
+ }
+
+ call_back_data.op = op;
+ call_back_data.retCode = 0;
+ call_back_data.lock_flag = lock_flag;
+
+ slapi_search_internal_callback_pb(aPb,
+ &call_back_data /* callback_data */,
+ NULL/* result_callback */,
+ __aclinit_handler,
+ NULL /* referral_callback */);
+
+ if (thisbeonly) {
+ slapi_ch_free((void **)&bval);
+ }
+
+ /*
+ * This frees the control oid, the bv_val and the control itself and the
+ * ctrls array mem by caling ldap_controls_free()--so we
+ * don't need to do it ourselves.
+ */
+ slapi_pblock_destroy (aPb);
+
+ return call_back_data.retCode;
+
+}
+
+/***************************************************************************
+*
+* __aclinit_handler
+*
+* For each entry, finds if there is any ACL in thet entry. If there is
+* then the ACL is processed and stored in the ACL LIST.
+*
+*
+* Input:
+*
+*
+* Returns:
+* None.
+*
+* Error Handling:
+* If any error found during the ACL generation, the ACL is
+* logged. Also, set in the callback_data so that caller can act upon it.
+*
+**************************************************************************/
+static int
+__aclinit_handler ( Slapi_Entry *e, void *callback_data)
+{
+ Slapi_Attr *attr;
+ aclinit_handler_callback_data_t *call_back_data =
+ (aclinit_handler_callback_data_t*)callback_data;
+ Slapi_DN *e_sdn;
+ int rv;
+ Slapi_Value *sval=NULL;
+
+ call_back_data->retCode = 0; /* assume success--if there's an error we overwrite it */
+ if (e != NULL) {
+
+ e_sdn = slapi_entry_get_sdn ( e );
+
+ /*
+ * Take the write lock around all the mods--so that
+ * other operations will see the acicache either before the whole mod
+ * or after but not, as it was before, during the mod.
+ * This is in line with the LDAP concept of the operation
+ * on the whole entry being the atomic unit.
+ *
+ */
+
+ if ( call_back_data->op == ACL_ADD_ACIS ) {
+ slapi_log_error ( SLAPI_LOG_ACL, plugin_name,
+ "Adding acis for entry '%s'\n", slapi_sdn_get_dn(e_sdn));
+ slapi_entry_attr_find ( e, aci_attr_type, &attr );
+
+ if ( attr ) {
+
+ const struct berval *attrValue;
+
+ int i;
+ if ( call_back_data->lock_flag == DO_TAKE_ACLCACHE_WRITELOCK) {
+ acllist_acicache_WRITE_LOCK();
+ }
+ i= slapi_attr_first_value ( attr, &sval );
+ while(i != -1) {
+ attrValue = slapi_value_get_berval(sval);
+
+ if ( 0 != (rv=acllist_insert_aci_needsLock (e_sdn, attrValue))) {
+ aclutil_print_err(rv, e_sdn, attrValue, NULL);
+
+ /* We got an error; Log it and then march along */
+ slapi_log_error ( SLAPI_LOG_FATAL, plugin_name,
+ "Error: This (%s) ACL will not be considered for evaluation"
+ " because of syntax errors.\n",
+ attrValue->bv_val ? attrValue->bv_val: "NULL", 0, 0);
+ call_back_data->retCode = rv;
+ }
+ i= slapi_attr_next_value( attr, i, &sval );
+ }/* while */
+ if ( call_back_data->lock_flag == DO_TAKE_ACLCACHE_WRITELOCK) {
+ acllist_acicache_WRITE_UNLOCK();
+ }
+ }
+ } else if (call_back_data->op == ACL_REMOVE_ACIS) {
+
+ /* Here we are deleting the acis. */
+ slapi_log_error ( SLAPI_LOG_ACL, plugin_name, "Removing acis\n");
+ if ( call_back_data->lock_flag == DO_TAKE_ACLCACHE_WRITELOCK) {
+ acllist_acicache_WRITE_LOCK();
+ }
+ if ( 0 != (rv=acllist_remove_aci_needsLock(e_sdn, NULL))) {
+ aclutil_print_err(rv, e_sdn, NULL, NULL);
+
+ /* We got an error; Log it and then march along */
+ slapi_log_error ( SLAPI_LOG_FATAL, plugin_name,
+ "Error: ACls not deleted from %s\n",
+ e_sdn, 0, 0);
+ call_back_data->retCode = rv;
+ }
+ if ( call_back_data->lock_flag == DO_TAKE_ACLCACHE_WRITELOCK) {
+ acllist_acicache_WRITE_UNLOCK();
+ }
+ }
+
+ }
+
+ /*
+ * If we get here it's success.
+ * The call_back_data->error is the error code that counts as it's the
+ * one that the original caller will see--this routine is called off a callbacl.
+ */
+
+ return ACL_FALSE; /* "local" error code--it's 0 */
+}
+/***************************************************************************
+*
+* __acl__RegisterAttributes
+*
+* Register all the attributes supported by the DS.
+*
+* Input:
+* None.
+*
+* Returns:
+* ACL_OK - No error
+* ACL_ERR - in case of errror
+*
+* Error Handling:
+* None.
+*
+**************************************************************************/
+static int
+__aclinit__RegisterAttributes(void)
+{
+
+ ACLMethod_t methodinfo;
+ NSErr_t errp;
+ int rv;
+
+ memset (&errp, 0, sizeof(NSErr_t));
+
+ rv = ACL_MethodRegister(&errp, DS_METHOD, &methodinfo);
+ if (rv < 0) {
+ acl_print_acllib_err(&errp, NULL);
+ slapi_log_error(SLAPI_LOG_FATAL, plugin_name,
+ "Unable to Register the methods\n", 0,0,0);
+ return ACL_ERR;
+ }
+ rv = ACL_MethodSetDefault (&errp, methodinfo);
+ if (rv < 0) {
+ acl_print_acllib_err(&errp, NULL);
+ slapi_log_error(SLAPI_LOG_FATAL, plugin_name,
+ "Unable to Set the default method\n", 0,0,0);
+ return ACL_ERR;
+ }
+ rv = ACL_AttrGetterRegister(&errp, ACL_ATTR_IP, DS_LASIpGetter,
+ methodinfo, ACL_DBTYPE_ANY, ACL_AT_FRONT, NULL);
+ if (rv < 0) {
+ acl_print_acllib_err(&errp, NULL);
+ slapi_log_error(SLAPI_LOG_FATAL, plugin_name,
+ "Unable to Register Attr ip\n", 0,0,0);
+ return ACL_ERR;
+ }
+ rv = ACL_AttrGetterRegister(&errp, ACL_ATTR_DNS, DS_LASDnsGetter,
+ methodinfo, ACL_DBTYPE_ANY, ACL_AT_FRONT, NULL);
+ if (rv < 0) {
+ acl_print_acllib_err(&errp, NULL);
+ slapi_log_error(SLAPI_LOG_FATAL, plugin_name,
+ "Unable to Register Attr dns\n", 0,0,0);
+ return ACL_ERR;
+ }
+ return ACL_OK;
+}
+
+/***************************************************************************
+*
+* __acl__RegisterLases
+* Register all the LASes supported by the DS.
+*
+* The DS doesnot support user/group. We have defined our own LAS
+* so that we can display/print an error when the LAS is invoked.
+* Input:
+* None.
+*
+* Returns:
+* ACL_OK - No error
+* ACL_ERR - in case of errror
+*
+* Error Handling:
+* None.
+*
+**************************************************************************/
+static int
+__aclinit__RegisterLases(void)
+{
+
+ if (ACL_LasRegister(NULL, DS_LAS_USER, (LASEvalFunc_t) DS_LASUserEval,
+ (LASFlushFunc_t) NULL) < 0) {
+ slapi_log_error (SLAPI_LOG_FATAL, plugin_name,
+ "Unable to register USER Las\n",0,0,0);
+ return ACL_ERR;
+ }
+ if (ACL_LasRegister(NULL, DS_LAS_GROUP, (LASEvalFunc_t) DS_LASGroupEval,
+ (LASFlushFunc_t) NULL) < 0) {
+ slapi_log_error (SLAPI_LOG_FATAL, plugin_name,
+ "Unable to register GROUP Las\n",0,0,0);
+ return ACL_ERR;
+ }
+ if (ACL_LasRegister(NULL, DS_LAS_GROUPDN, (LASEvalFunc_t)DS_LASGroupDnEval,
+ (LASFlushFunc_t)NULL) < 0) {
+ slapi_log_error (SLAPI_LOG_FATAL, plugin_name,
+ "Unable to register GROUPDN Las\n",0,0,0);
+ return ACL_ERR;
+ }
+ if (ACL_LasRegister(NULL, DS_LAS_ROLEDN, (LASEvalFunc_t)DS_LASRoleDnEval,
+ (LASFlushFunc_t)NULL) < 0) {
+ slapi_log_error (SLAPI_LOG_FATAL, plugin_name,
+ "Unable to register ROLEDN Las\n",0,0,0);
+ return ACL_ERR;
+ }
+ if (ACL_LasRegister(NULL, DS_LAS_USERDN, (LASEvalFunc_t)DS_LASUserDnEval,
+ (LASFlushFunc_t)NULL) < 0) {
+ slapi_log_error (SLAPI_LOG_FATAL, plugin_name,
+ "Unable to register USERDN Las\n",0,0,0);
+ return ACL_ERR;
+ }
+ if (ACL_LasRegister(NULL, DS_LAS_USERDNATTR,
+ (LASEvalFunc_t)DS_LASUserDnAttrEval,
+ (LASFlushFunc_t)NULL) < 0) {
+ slapi_log_error (SLAPI_LOG_FATAL, plugin_name,
+ "Unable to register USERDNATTR Las\n",0,0,0);
+ return ACL_ERR;
+ }
+ if (ACL_LasRegister(NULL, DS_LAS_AUTHMETHOD,
+ (LASEvalFunc_t)DS_LASAuthMethodEval,
+ (LASFlushFunc_t)NULL) < 0) {
+ slapi_log_error (SLAPI_LOG_FATAL, plugin_name,
+ "Unable to register CLIENTAUTHTYPE Las\n",0,0,0);
+ return ACL_ERR;
+ }
+ if (ACL_LasRegister(NULL, DS_LAS_GROUPDNATTR,
+ (LASEvalFunc_t)DS_LASGroupDnAttrEval,
+ (LASFlushFunc_t)NULL) < 0) {
+ slapi_log_error (SLAPI_LOG_FATAL, plugin_name,
+ "Unable to register GROUPDNATTR Las\n",0,0,0);
+ return ACL_ERR;
+ }
+ if (ACL_LasRegister(NULL, DS_LAS_USERATTR,
+ (LASEvalFunc_t)DS_LASUserAttrEval,
+ (LASFlushFunc_t)NULL) < 0) {
+ slapi_log_error (SLAPI_LOG_FATAL, plugin_name,
+ "Unable to register USERATTR Las\n",0,0,0);
+ return ACL_ERR;
+ }
+ return ACL_OK;
+}
diff --git a/ldap/servers/plugins/acl/acllas.c b/ldap/servers/plugins/acl/acllas.c
new file mode 100644
index 00000000..0179b0e6
--- /dev/null
+++ b/ldap/servers/plugins/acl/acllas.c
@@ -0,0 +1,3848 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#include <ipfstruct.h>
+#include "acl.h"
+
+/*
+ A word on this file:
+
+ The various routines here implement each component of the subject of an aci
+ eg. "groupdn", "userdn","roledn", "userattr" etc.
+ They are responsible for evaluating each individual keyword not for doing
+ the boolean combination of these keywords, nor for combining multiple
+ allow()/deny() statements--that's libaccess's job.
+ For example, for "groupdn", DS_LASGroupDnEval might have to evaluate
+ something like this:
+
+ "groupdn = "ldap:///cn=G1,o=sun.com || ldap:///cn=G2,o=sun.com"
+
+ The "=" here may be "!=" as well and these routines take care of the
+ comparator.
+
+ These rotuines get called via acl__TestRights(), which calls
+ ACL_EvalTestRights() a libaccess routine (the immediately calling routine is
+ ACLEvalAce() in oneeval.cpp).
+
+ They should return LAS_EVAL_TRUE, if that keyword component evaluates to
+ TRUE, LAS_EVAL_FALSE if it evaluates to FALSE and LAS_EVAL_FAIL if an
+ error occurrs during evaluation. Note that once any component of a subject
+ returns LAS_EVAL_FAIL, the evaluation in libaccess stops and the whole
+ subject does not match and that aci is not applied.
+*/
+
+/*
+
+ A word on three-valued logic:
+
+ In general when you do boolean combination of terms some of which
+ may evaluate to UNDEFINED then you need to define what the combination
+ means.
+ So, for example libaccess implements a scheme which once UNDEFINED
+ is returned for a term, it bales out of the
+ evaluation and the whole expression evaluates to UNDEFINED.
+ In this case the aci will not apply.
+ On the other hand LDAP filters (cf. rfc2251 4.5.1) say that for OR,
+ an expression will
+ evaluate to TRUE if any term is TRUE, even if some terms are UNDEFINED.
+ Other off the cuff options might be to redefine UNDEFINED to be FALSE,
+ or TRUE.
+
+ Which is best ?
+
+ Well it probably depends on exactly what is to decided based on the
+ evaluation of the logical expression. However, the final suggestion is
+ almost certainly
+ bad--you are unlikely to want to take an action based on an undefined
+ result and
+ defining UNDEFINED to be either TRUE or FALSE may result in the overall
+ expression
+ returning TRUE--a security hole. The only case this might work is if you
+ are dealing with restricted
+ expressions eg. terms may only be AND'ed togther--in this case defining
+ UNDEFINED to be FALSE would guarantee a result of FALSE.
+
+ The libaccess approach of returning UNDEFINED once an UNDEFINED is
+ encountered during
+ evaluation is not too bad--at least it guarantees that no aci will apply
+ based on an
+ undefined value. However, with an aci like this "...allow(all) A or B"
+ where A returned UNDEFINED, you might be disappointed not to receive the
+ rights if it was B that
+ was granting you the rights and evaluation of A, which has nothing to do
+ with you, returns UNDEFINED. In the case of an aci like
+ "...deny(all) A or B" then the same
+ situation is arguably a security hole. Note that this scheme also makes
+ the final result
+ dependent on the evaluation order and so if the evaluation engine does
+ anything fancy internally (eg. reordering the terms in an OR so that fast
+ to evaluate ones came first) then
+ this would need to be documented so that a user (or a tool) could look at
+ the external syntax and figure out the result of the evaluation.
+ Also it breaks commutivity and De Morgans law.
+
+ The LDAP filter scheme is starting to look good--it solves the problems of
+ the
+ libaccess approach, makes the final result of an expression independent of
+ the evaluation order and
+ gives you back commutivity of OR and AND. De Morgans is still broken, but
+ that's because of the asymmetry of behaviour of UNDEFINED with OR and AND.
+
+ So...?
+
+ For acis, in general it can look like this:
+
+ "...allow(rights)(LogicalCombinationofBindRule);
+ deny(LogicalCombinationOfBindRule)...."
+
+ A BindRule is one of the "userdn", "groupdn" or "userattr" things and it
+ can look like this:
+
+ "groupdn = "ldap:///cn=G1,o=sun.com || ldap:///cn=G2,o=sun.com"
+
+ The "=" here may be "!=" as well and these routines take care of the
+ comparator.
+
+ For "userattr" keywords a mutilvalued attribute amounts a logical OR of the
+ individual values. There is also a logical OR over the different levels
+ as specified by the "parent" keyword.
+
+ In fact there are three levels of logical combination:
+
+ 1. In the aclplugin:
+ The "||" and "!=" combinator for BindRule keywords like userdn and
+ groupdn.
+ The fact that for the "userattr" keyword, a mutilvalued attribute is
+ evaluated as "||". Same for the different levels.
+ 2. In libaccess:
+ The logical combination of BindRules.
+ 3. In libaccess:
+ The evaluation of multiple BindRules seperated by ";", which means OR.
+
+ The LDAP filter three-valued logic SHOULD be applied to each level but
+ here's the way it works right now:
+
+ 1. At this level it depends....
+
+ DS_LASIpGetter - get attr for IP -
+ returns ip address or LAS_EVAL_FAIL for error.
+ no logical combination.
+ DS_LASDnsGetter - get attr for DNS-
+ returns dns name or LAS_EVAL_FAIL for error
+ no logical combination.
+ DS_LASUserDnEval - LAS Evaluation for USERDN -
+ three-valued logic
+ logical combination: || and !=
+ DS_LASGroupDnEval - LAS Evaluation for GROUPDN -
+ three-valued logic
+ logical combination: || and !=
+ DS_LASRoleDnEval - LAS Evaluation for ROLEDN -
+ three-valued logic
+ logical combination: || and !=
+ DS_LASUserDnAttrEval - LAS Evaluation for USERDNATTR -
+ three-valued logic
+ logical combination || (over specified attribute values and
+ parent keyword levels), !=
+ DS_LASAuthMethodEval - LAS Evaluation for AUTHMETHOD -
+ three-valued logic ( logical combinations: !=)
+ DS_LASGroupDnAttrEval - LAS Evaluation for GROUPDNATTR -
+ three-valued logic
+ logical combination || (over specified attribute values and
+ parent keyword levels), !=
+ DS_LASUserAttrEval - LAS Evaluation for USERATTR -
+ USER, GROUPDN and ROLEDN as above.
+ LDAPURL -- three-valued logic (logical combinations: || over
+ specified attribute vales, !=)
+ attrname#attrvalue -- three-valued logic, logical combination:!=
+
+ 2. The libaccess scheme applies at this level.
+ 3. The LDAP filter three-valued logic applies at this level.
+
+ Example of realistic, non-bizarre things that cause evaluation of a
+ BindRule to be undefined are exceeding some resource limits (nesting level,
+ lookthrough limit) in group membership evaluation, or trying to get ADD
+ permission from the "userattr" keyword at "parent" level 0.
+ Note that not everything that might be construed as an error needs to be
+ taken as UNDEFINED. For example, things like not finding a user or an
+ attribute in an entry can be defined away as TRUE or FALSE. eg. in an
+ LDAP filter (cn=rob) applied to an entry where cn is not present is FALSE,
+ not UNDEFINED. Similarly, if the number of levels in a parent keyword
+ exceeds the allowed limit, we just ignore the rest--though this
+ is a syntax error which should be detected at parse time.
+
+
+*/
+
+/* To get around warning: declared in ldapserver/lib/ldaputil/ldaputili.h */
+extern int ldapu_member_certificate_match (void* cert, const char* desc);
+
+/****************************************************************************/
+/* Defines, Constants, ande Declarations */
+/****************************************************************************/
+static char* const type_objectClass = "objectclass";
+static char* const filter_groups = "(|(objectclass=groupOfNames) (objectclass=groupOfUniqueNames)(objectclass=groupOfCertificates)(objectclass=groupOfURLs))";
+static char* const type_member = "member";
+static char* const type_uniquemember = "uniquemember";
+static char* const type_memberURL = "memberURL";
+static char* const type_memberCert = "memberCertificateDescription";
+
+/* cache strategy for groups */
+#define ACLLAS_CACHE_MEMBER_GROUPS 0x1
+#define ACLLAS_CACHE_NOT_MEMBER_GROUPS 0x2
+#define ACLLAS_CACHE_ALL_GROUPS 0x3
+
+/****************************************************************************/
+/* prototypes */
+/****************************************************************************/
+static int acllas__handle_group_entry(Slapi_Entry *, void *);
+static int acllas__user_ismember_of_group(struct acl_pblock *aclpb,
+ char* groupDN,
+ char* clientDN,
+ int cache_status,
+ CERTCertificate *clientCert);
+static int acllas__user_has_role( struct acl_pblock *aclpb,
+ Slapi_DN *roleDN, Slapi_DN *clientDn);
+static int acllas__add_allgroups (Slapi_Entry* e, void *callback_data);
+static int acllas__eval_memberGroupDnAttr (char *attrName,
+ Slapi_Entry *e,
+ char *n_clientdn,
+ struct acl_pblock *aclpb);
+static int acllas__verify_client (Slapi_Entry* e, void *callback_data);
+static char* acllas__dn_parent( char *dn, int level);
+static int acllas__get_members (Slapi_Entry* e, void *callback_data);
+static int acllas__client_match_URL (struct acl_pblock *aclpb,
+ char *n_dn, char *url );
+static int acllas__handle_client_search (Slapi_Entry *e, void *callback_data);
+static int __acllas_setup ( NSErr_t *errp, char *attr_name, CmpOp_t comparator,
+ char *attr_pattern, int *cachable, void **LAS_cookie,
+ PList_t subject, PList_t resource, PList_t auth_info,
+ PList_t global_auth, char *lasType, char *lasName, lasInfo *linfo);
+int
+aclutil_evaluate_macro( char * user, lasInfo *lasinfo,
+ acl_eval_types evalType );
+static int
+acllas_eval_one_user( struct acl_pblock *aclpb,
+ char * clientDN, char *userKeyword);
+static int
+acllas_eval_one_group(char *group, lasInfo *lasinfo);
+static int
+acllas_eval_one_role(char *role, lasInfo *lasinfo);
+static char **
+acllas_replace_dn_macro( char *rule, char *matched_val, lasInfo *lasinfo);
+static char **
+acllas_replace_attr_macro( char *rule, lasInfo *lasinfo);
+static int
+acllas_eval_one_target_filter( char * str, Slapi_Entry *e);
+
+/****************************************************************************/
+
+int
+DS_LASIpGetter(NSErr_t *errp, PList_t subject, PList_t resource, PList_t
+ auth_info, PList_t global_auth, void *arg)
+{
+
+ struct acl_pblock *aclpb = NULL;
+ IPAddr_t ip=0;
+ PRNetAddr client_praddr;
+ struct in_addr client_addr;
+ int rv;
+
+
+ rv = ACL_GetAttribute(errp, DS_PROP_ACLPB, (void **)&aclpb,
+ subject, resource, auth_info, global_auth);
+ if ( rv != LAS_EVAL_TRUE || ( NULL == aclpb )) {
+ acl_print_acllib_err(errp, NULL);
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "DS_LASIpGetter:Unable to get the ACLPB(%d)\n", rv,0,0);
+ return LAS_EVAL_FAIL;
+ }
+
+ if ( slapi_pblock_get( aclpb->aclpb_pblock, SLAPI_CONN_CLIENTNETADDR,
+ &client_praddr ) != 0 ) {
+ slapi_log_error( SLAPI_LOG_FATAL, plugin_name, "Could not get client IP.\n" );
+ return( LAS_EVAL_FAIL );
+ }
+
+ if ( !PR_IsNetAddrType(&client_praddr, PR_IpAddrV4Mapped) ) {
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "Client address is IPv6. ACLs only support IPv4 addresses so far.\n");
+ return( LAS_EVAL_FAIL );
+ }
+
+ client_addr.s_addr = client_praddr.ipv6.ip.pr_s6_addr32[3];
+
+ ip = (IPAddr_t) ntohl( client_addr.s_addr );
+ rv = PListInitProp(subject, 0, ACL_ATTR_IP, (void *)ip, NULL);
+
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "Returning client ip address '%s'\n",
+ (slapi_is_loglevel_set(SLAPI_LOG_ACL) ? inet_ntoa(client_addr) : ""));
+
+ return LAS_EVAL_TRUE;
+
+}
+
+/*
+ * This is called from the libaccess code when it needs to find a dns name.
+ * It's called from ACL_GetAttribute() when it finds that ACL_ATTR_DNS is
+ * not already part of the proplist.
+ *
+*/
+
+int
+DS_LASDnsGetter(NSErr_t *errp, PList_t subject, PList_t resource, PList_t
+ auth_info, PList_t global_auth, void *arg)
+{
+ struct acl_pblock *aclpb = NULL;
+ PRNetAddr client_praddr;
+ PRHostEnt *hp;
+ char *dnsName = NULL;
+ int rv;
+ struct berval **clientDns;
+
+
+ rv = ACL_GetAttribute(errp, DS_PROP_ACLPB, (void **)&aclpb,
+ subject, resource, auth_info, global_auth);
+ if ( rv != LAS_EVAL_TRUE || ( NULL == aclpb )) {
+ acl_print_acllib_err(errp, NULL);
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "DS_LASDnsGetter:Unable to get the ACLPB(%d)\n", rv,0,0);
+ return LAS_EVAL_FAIL;
+ }
+
+ if ( slapi_pblock_get( aclpb->aclpb_pblock, SLAPI_CLIENT_DNS, &clientDns ) != 0 ) {
+ slapi_log_error( SLAPI_LOG_FATAL, plugin_name, "Could not get client IP.\n" );
+ return( LAS_EVAL_FAIL );
+ }
+
+ /*
+ * If the client hostname has already been put into the pblock then
+ * use that. Otherwise we work it out and add it ourselves.
+ * This info is connection-lifetime so with multiple operaitons on the same
+ * connection we will only do the calculation once.
+ *
+ * rbyrneXXX surely this code would be better in connection.c so
+ * the name would be just there waiting for us, and everyone else.
+ *
+ */
+
+ if ( clientDns && clientDns[0] != NULL && clientDns[0]->bv_val ) {
+ dnsName = clientDns[0]->bv_val;
+ } else {
+ struct berval **dnsList;
+ char buf[PR_NETDB_BUF_SIZE];
+
+ if ( slapi_pblock_get( aclpb->aclpb_pblock, SLAPI_CONN_CLIENTNETADDR, &client_praddr ) != 0 ) {
+
+ slapi_log_error( SLAPI_LOG_FATAL, plugin_name, "Could not get client IP.\n" );
+ return( LAS_EVAL_FAIL );
+ }
+ hp = (PRHostEnt *)slapi_ch_malloc( sizeof(PRHostEnt) );
+ if ( PR_GetHostByAddr( &(client_praddr), (char *)buf, sizeof(buf), hp ) == PR_SUCCESS ) {
+ if ( hp->h_name != NULL ) {
+ dnsList = (struct berval**)
+ slapi_ch_calloc (1, sizeof(struct berval*) * (1 + 1));
+ *dnsList = (struct berval*)
+ slapi_ch_calloc ( 1, sizeof(struct berval));
+ dnsName = (*dnsList)->bv_val = slapi_ch_strdup( hp->h_name );
+ (*dnsList)->bv_len = strlen ( (*dnsList)->bv_val );
+ slapi_pblock_set( aclpb->aclpb_pblock, SLAPI_CLIENT_DNS, &dnsList );
+ }
+ }
+ slapi_ch_free( (void **)&hp );
+ }
+
+ if ( NULL == dnsName ) return LAS_EVAL_FAIL;
+
+ rv = PListInitProp(subject, 0, ACL_ATTR_DNS, dnsName, NULL);
+ if (rv < 0) {
+ slapi_log_error ( SLAPI_LOG_ACL, plugin_name,
+ "DS_LASDnsGetter:Couldn't set the DNS property(%d)\n", rv );
+ return LAS_EVAL_FAIL;
+ }
+ slapi_log_error ( SLAPI_LOG_ACL, plugin_name, "DNS name: %s\n", dnsName );
+ return LAS_EVAL_TRUE;
+
+}
+/***************************************************************************/
+/* New LASes */
+/* */
+/* 1. user, groups. -- stubs to report errors. Not supported. */
+/* 2. userdn */
+/* 3. groupdn */
+/* 4. userdnattr */
+/* 5. authmethod */
+/* 6. groupdnattr */
+/* 7. roledn */
+/* */
+/* */
+/***************************************************************************/
+int
+DS_LASUserEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator,
+ char *attr_pattern, int *cachable, void **LAS_cookie,
+ PList_t subject, PList_t resource, PList_t auth_info,
+ PList_t global_auth)
+{
+ slapi_log_error(SLAPI_LOG_FATAL, plugin_name,
+ "User LAS is not supported in the ACL\n",0,0,0);
+
+ return LAS_EVAL_INVALID;
+}
+
+int
+DS_LASGroupEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator,
+ char *attr_pattern, int *cachable, void **LAS_cookie,
+ PList_t subject, PList_t resource, PList_t auth_info,
+ PList_t global_auth)
+{
+ slapi_log_error(SLAPI_LOG_FATAL, plugin_name,
+ "Group LAS is not supported in the ACL\n",0,0,0);
+
+ return LAS_EVAL_INVALID;
+}
+
+/***************************************************************************
+*
+* DS_LASUserDnEval
+* Evaluate the "userdn" LAS. See if the user has rights.
+*
+* Input:
+* attr_name The string "userdn" - in lower case.
+* comparator CMP_OP_EQ or CMP_OP_NE only
+* attr_pattern A comma-separated list of users
+* cachable Always set to FALSE.
+* subject Subject property list
+* resource Resource property list
+* auth_info Authentication info, if any
+*
+* Returns:
+* retcode The usual LAS return codes.
+*
+* Error Handling:
+* None.
+*
+**************************************************************************/
+int
+DS_LASUserDnEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator,
+ char *attr_pattern, int *cachable, void **LAS_cookie,
+ PList_t subject, PList_t resource, PList_t auth_info,
+ PList_t global_auth)
+{
+
+ char *users = NULL;
+ char *s_user, *user = NULL;
+ char *ptr = NULL;
+ char *end_dn = NULL;
+ char *n_edn = NULL;
+ char *parent_dn = NULL;
+ int matched;
+ int rc;
+ short len;
+ char *s = NULL;
+ const size_t LDAP_URL_prefix_len = strlen(LDAP_URL_prefix);
+ lasInfo lasinfo;
+ int got_undefined = 0;
+
+ if ( 0 != (rc = __acllas_setup (errp, attr_name, comparator,
+ attr_pattern,cachable,LAS_cookie,
+ subject, resource, auth_info,global_auth,
+ DS_LAS_USERDN, "DS_LASUserDnEval", &lasinfo )) ) {
+ return LAS_EVAL_FAIL;
+ }
+
+ users = slapi_ch_strdup(attr_pattern);
+ user = users;
+ matched = ACL_FALSE;
+
+ /* check if the clientdn is one of the users */
+ while(user != 0 && *user != 0 && matched != ACL_TRUE ) {
+
+ /* ignore leading whitespace */
+ while(ldap_utf8isspace(user))
+ LDAP_UTF8INC(user);
+
+ /* Now we must see the userdn in the following
+ ** formats:
+ **
+ ** The following formats are supported:
+ **
+ ** 1. The DN itself:
+ ** allow (read) userdn = "ldap:///cn=prasanta, ..."
+ **
+ ** 2. keyword SELF:
+ ** allow (write)
+ ** userdn = "ldap:///self"
+ **
+ ** 3. Pattern:
+ ** deny (read) userdn = "ldap:///cn=*, o=netscape, c = us";
+ **
+ ** 4. Anonymous user
+ ** deny (read, write) userdn = "ldap:///anyone"
+ **
+ ** 5. All users (All authenticated users)
+ ** allow (search) ** userdn = "ldap:///all"
+ ** 6. parent "ldap:///parent"
+ ** 7. Synamic users using the URL
+ **
+ **
+ ** DNs must be separated by "||". Ex:
+ ** allow (read)
+ ** userdn = "ldap:///DN1 || ldap:///DN2"
+ */
+
+
+ /* The DN is now "ldap:///DN"
+ ** remove the "ldap:///" part
+ */
+ if (strncasecmp (user, LDAP_URL_prefix,
+ LDAP_URL_prefix_len) == 0) {
+ s_user = user;
+ user += LDAP_URL_prefix_len;
+
+ } else {
+ char ebuf[ BUFSIZ ];
+ slapi_log_error(SLAPI_LOG_FATAL, plugin_name,
+ "DS_LASUserDnEval:Syntax error(%s)\n",
+ escape_string_with_punctuation( user, ebuf ), 0,0);
+ return LAS_EVAL_FAIL;
+ }
+
+ /* Now we have the starting point of the "userdn" */
+ if ((end_dn = strstr(user, "||")) != NULL) {
+ auto char *t = end_dn;
+ LDAP_UTF8INC(end_dn);
+ LDAP_UTF8INC(end_dn);
+ *t = 0;
+ }
+
+ /* Now user is a null terminated string */
+
+ if (*user) {
+ while(ldap_utf8isspace(user))
+ LDAP_UTF8INC(user);
+ /* ignore trailing whitespace */
+ len = strlen(user);
+ ptr = user+len-1;
+ while(ldap_utf8isspace(ptr)){ *ptr = '\0'; LDAP_UTF8DEC(ptr); }
+ }
+
+ /*
+ ** Check , if the user is a anonymous user. In that case
+ ** We must find the rule "ldap:///anyone"
+ */
+ if (lasinfo.anomUser) {
+ if (strcasecmp(user, "anyone") == 0 ) {
+ /* matches -- anonymous user */
+ matched = ACL_TRUE;
+ break;
+ }
+ } else {
+ /* URL format */
+
+ if ((s = strstr (user, ACL_RULE_MACRO_DN_KEY)) != NULL ||
+ (s = strstr (user, ACL_RULE_MACRO_DN_LEVELS_KEY)) != NULL ||
+ (s = strstr (user, ACL_RULE_MACRO_ATTR_KEY)) != NULL) {
+
+ matched = aclutil_evaluate_macro( s_user, &lasinfo,
+ ACL_EVAL_USER);
+ if (matched == ACL_TRUE) {
+ break;
+ }
+
+ } else if ((s = strchr (user, '?'))!= NULL) {
+ /* URL format */
+ if (acllas__client_match_URL ( lasinfo.aclpb, lasinfo.clientDn,
+ s_user) == ACL_TRUE) {
+ matched = ACL_TRUE;
+ break;
+ }
+ } else if (strcasecmp(user, "anyone") == 0 ) {
+ /* Anyone means anyone in the world */
+ matched = ACL_TRUE;
+ break;
+ } else if (strcasecmp(user, "self") == 0) {
+ if (n_edn == NULL) {
+ n_edn = slapi_entry_get_ndn ( lasinfo.resourceEntry );
+ }
+ if (slapi_utf8casecmp((ACLUCHP)lasinfo.clientDn, (ACLUCHP)n_edn) == 0)
+ matched = ACL_TRUE;
+ break;
+ } else if (strcasecmp(user, "parent") == 0) {
+ if (n_edn == NULL) {
+ n_edn = slapi_entry_get_ndn ( lasinfo.resourceEntry );
+ }
+ /* get the parent */
+ parent_dn = slapi_dn_parent(n_edn);
+ if (parent_dn &&
+ slapi_utf8casecmp ((ACLUCHP)lasinfo.clientDn, (ACLUCHP)parent_dn) == 0)
+ matched = ACL_TRUE;
+
+ if (parent_dn) slapi_ch_free ( (void **) &parent_dn );
+ break;
+ } else if (strcasecmp(user, "all") == 0) {
+ /* matches -- */
+ matched = ACL_TRUE;
+ break;
+ } else if (strchr(user, '*')) {
+ char line[200];
+ char *lineptr = &line[0];
+ char *newline = NULL;
+ int lenu = 0;
+ Slapi_Filter *f = NULL;
+ char *tt;
+ int filterChoice;
+
+ /*
+ ** what we are doing is faking the str2simple()
+ ** function with a "userdn = "user")
+ */
+ for (tt = user; *tt; tt++)
+ *tt = TOLOWER ( *tt );
+
+ if ((lenu = strlen(user)) > 190) { /* 200 - 9 for "(userdn=%s)" */
+ newline = slapi_ch_malloc(lenu + 10);
+ lineptr = newline;
+ }
+
+ sprintf (lineptr, "(userdn=%s)", user);
+ if ((f = slapi_str2filter (lineptr)) == NULL) {
+ if (newline) slapi_ch_free((void **) &newline);
+ /* try the next one */
+ break;
+ }
+ if (newline) slapi_ch_free((void **) &newline);
+ filterChoice = slapi_filter_get_choice ( f );
+
+ if (( filterChoice != LDAP_FILTER_SUBSTRINGS) &&
+ ( filterChoice != LDAP_FILTER_PRESENT)) {
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "DS_LASUserDnEval:Error in gen. filter(%s)\n", user);
+ }
+ if ((rc = acl_match_substring( f,
+ lasinfo.clientDn,
+ 1 /*exact match */)
+ ) == ACL_TRUE) {
+ matched = ACL_TRUE;
+ slapi_filter_free(f,1);
+ break;
+ }
+ if (rc == ACL_ERR) {
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "DS_LASUserDnEval:Error in matching patteren(%s)\n",
+ user,0,0);
+ }
+ slapi_filter_free(f,1);
+ } else {
+ /* Must be a simple dn then */
+ if (slapi_utf8casecmp((ACLUCHP)lasinfo.clientDn,
+ (ACLUCHP)slapi_dn_normalize(user)) == 0) {
+ matched = ACL_TRUE;
+ break;
+ }
+ }
+ }
+
+ if ( matched == ACL_DONT_KNOW ) {
+ /* record this but keep going--maybe another user will evaluate to TRUE */
+ got_undefined = 1;
+ }
+ /* Nothing matched -- try the next DN */
+ user = end_dn;
+ } /* end of while */
+
+ slapi_ch_free ( (void **) &users);
+
+ /*
+ * If no terms were undefined, then evaluate as normal.
+ * If there was an undefined term, but another one was TRUE, then we also evaluate
+ * as normal. Otherwise, the whole expression is UNDEFINED.
+ */
+ if ( matched == ACL_TRUE || !got_undefined ) {
+ if (comparator == CMP_OP_EQ) {
+ rc = (matched == ACL_TRUE ? LAS_EVAL_TRUE : LAS_EVAL_FALSE);
+ } else {
+ rc = (matched == ACL_TRUE ? LAS_EVAL_FALSE : LAS_EVAL_TRUE);
+ }
+ } else {
+ rc = LAS_EVAL_FAIL;
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "Returning UNDEFINED for userdn evaluation.\n");
+ }
+
+ return rc;
+}
+
+/***************************************************************************
+*
+* DS_LASGroupDnEval
+*
+*
+* Input:
+* attr_name The string "userdn" - in lower case.
+* comparator CMP_OP_EQ or CMP_OP_NE only
+* attr_pattern A comma-separated list of users
+* cachable Always set to FALSE.
+* subject Subject property list
+* resource Resource property list
+* auth_info Authentication info, if any
+*
+* Returns:
+* retcode The usual LAS return code
+* If the client is in any of the groups mentioned this groupdn keywrod
+* then returns LAS_EVAL_TRUE, if he's not in any LAS_EVAL_FALSE.
+* If any of the membership evaluations fail, then it goes on to evaluate the
+* others.
+*
+* Error Handling:
+* None.
+*
+**************************************************************************/
+int
+DS_LASGroupDnEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator,
+ char *attr_pattern, int *cachable, void **LAS_cookie,
+ PList_t subject, PList_t resource, PList_t auth_info,
+ PList_t global_auth)
+{
+
+ char *groups;
+ char *groupName;
+ char *ptr;
+ char *end_dn;
+ int matched;
+ int rc;
+ int len;
+ const size_t LDAP_URL_prefix_len = strlen(LDAP_URL_prefix);
+ int any_group = 0;
+ lasInfo lasinfo;
+ int got_undefined = 0;
+
+ /* the setup should not fail under normal operation */
+ if ( 0 != (rc = __acllas_setup (errp, attr_name, comparator,
+ attr_pattern,cachable,LAS_cookie,
+ subject, resource, auth_info,global_auth,
+ DS_LAS_GROUPDN, "DS_LASGroupDnEval", &lasinfo )) ) {
+ return LAS_EVAL_FAIL;
+ }
+
+ groups = slapi_ch_strdup(attr_pattern);
+ groupName = groups;
+ matched = ACL_FALSE;
+
+ /* check if the groupdn is one of the users */
+ while(groupName != 0 && *groupName != 0 && matched != ACL_TRUE) {
+
+ /* ignore leading whitespace */
+ while(ldap_utf8isspace(groupName))
+ LDAP_UTF8INC(groupName);
+
+ /*
+ ** The syntax allowed for the groupdn is
+ **
+ ** Example:
+ ** groupdn = "ldap:///dn1 || ldap:///dn2";
+ **
+ */
+
+ if (strncasecmp (groupName, LDAP_URL_prefix,
+ LDAP_URL_prefix_len) == 0) {
+ groupName += LDAP_URL_prefix_len;
+ } else {
+ char ebuf[ BUFSIZ ];
+ slapi_log_error(SLAPI_LOG_FATAL, plugin_name,
+ "DS_LASGroupDnEval:Syntax error(%s)\n",
+ escape_string_with_punctuation( groupName, ebuf ),0,0);
+ }
+
+ /* Now we have the starting point of the "groupdn" */
+ if ((end_dn = strstr(groupName, "||")) != NULL) {
+ auto char *t = end_dn;
+ LDAP_UTF8INC(end_dn);
+ LDAP_UTF8INC(end_dn);
+ *t = 0;
+ }
+
+ if (*groupName) {
+ while(ldap_utf8isspace(groupName))
+ LDAP_UTF8INC(groupName);
+ /* ignore trailing whitespace */
+ len = strlen(groupName);
+ ptr = groupName+len-1;
+ while(ldap_utf8isspace(ptr)) { *ptr = '\0'; LDAP_UTF8DEC(ptr); }
+ }
+
+ /*
+ ** Now we have the DN of the group. Evaluate the "clientdn"
+ ** and see if the user is a member of the group.
+ */
+ if (0 == (strcasecmp(groupName, "anyone"))) {
+ any_group = 1;
+ }
+
+ if (any_group) {
+ /* anyone in the world */
+ matched = ACL_TRUE;
+ break;
+ } else if ( lasinfo.anomUser &&
+ (lasinfo.aclpb->aclpb_clientcert == NULL) && (!any_group)) {
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "Group not evaluated(%s)\n", groupName);
+ break;
+ } else {
+ char *s;
+
+ if ((s = strstr (groupName, ACL_RULE_MACRO_DN_KEY)) != NULL ||
+ (s = strstr (groupName, ACL_RULE_MACRO_DN_LEVELS_KEY)) != NULL ||
+ (s = strstr (groupName, ACL_RULE_MACRO_ATTR_KEY)) != NULL) {
+
+ matched = aclutil_evaluate_macro( groupName, &lasinfo,
+ ACL_EVAL_GROUP);
+ slapi_log_error ( SLAPI_LOG_ACL, plugin_name,
+ "DS_LASGroupDnEval: Param group name:%s\n",
+ groupName);
+ } else {/* normal evaluation */
+
+ matched = acllas_eval_one_group( groupName, &lasinfo);
+
+ }
+
+ if ( matched == ACL_TRUE ) {
+ break;
+ } else if ( matched == ACL_DONT_KNOW ) {
+ /* record this but keep going--maybe another group will evaluate to TRUE */
+ got_undefined = 1;
+ }
+ }
+ /* Nothing matched -- try the next DN */
+ groupName = end_dn;
+
+ } /* end of while */
+
+ /*
+ * If no terms were undefined, then evaluate as normal.
+ * If there was an undefined term, but another one was TRUE, then we also evaluate
+ * as normal. Otherwise, the whole expression is UNDEFINED.
+ */
+ if ( matched == ACL_TRUE || !got_undefined ) {
+ if (comparator == CMP_OP_EQ) {
+ rc = (matched == ACL_TRUE ? LAS_EVAL_TRUE : LAS_EVAL_FALSE);
+ } else {
+ rc = (matched == ACL_TRUE ? LAS_EVAL_FALSE : LAS_EVAL_TRUE);
+ }
+ } else {
+ rc = LAS_EVAL_FAIL;
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "Returning UNDEFINED for groupdn evaluation.\n");
+ }
+
+ slapi_ch_free ((void**) &groups);
+ return rc;
+}
+/***************************************************************************
+*
+* DS_LASRoleDnEval
+*
+*
+* Input:
+* attr_name The string "roledn" - in lower case.
+* comparator CMP_OP_EQ or CMP_OP_NE only
+* attr_pattern A "||" sperated list of roles
+* cachable Always set to FALSE.
+* subject Subject property list
+* resource Resource property list
+* auth_info Authentication info, if any
+*
+* Returns:
+* retcode The usual LAS return codes.
+*
+* Error Handling:
+* None.
+*
+**************************************************************************/
+int
+DS_LASRoleDnEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator,
+ char *attr_pattern, int *cachable, void **LAS_cookie,
+ PList_t subject, PList_t resource, PList_t auth_info,
+ PList_t global_auth)
+{
+
+ char *roles;
+ char *role;
+ char *ptr;
+ char *end_dn;
+ int matched;
+ int rc;
+ int len;
+ const size_t LDAP_URL_prefix_len = strlen(LDAP_URL_prefix);
+ int any_role = 0;
+ lasInfo lasinfo;
+ int got_undefined = 0;
+
+ if ( 0 != (rc = __acllas_setup (errp, attr_name, comparator,
+ attr_pattern,cachable,LAS_cookie,
+ subject, resource, auth_info,global_auth,
+ DS_LAS_ROLEDN, "DS_LASRoleDnEval",
+ &lasinfo )) ) {
+ return LAS_EVAL_FALSE;
+ }
+
+
+ roles = slapi_ch_strdup(attr_pattern);
+ role = roles;
+ matched = ACL_FALSE;
+
+ /* check if the roledn is one of the users */
+ while(role != 0 && *role != 0 && matched != ACL_TRUE) {
+
+ /* ignore leading whitespace */
+ while(ldap_utf8isspace(role))
+ LDAP_UTF8INC(role);
+
+ /*
+ ** The syntax allowed for the roledn is
+ **
+ ** Example:
+ ** roledn = "ldap:///roledn1 || ldap:///roledn2";
+ **
+ */
+
+ if (strncasecmp (role, LDAP_URL_prefix,
+ LDAP_URL_prefix_len) == 0) {
+ role += LDAP_URL_prefix_len;
+ } else {
+ char ebuf[ BUFSIZ ];
+ slapi_log_error(SLAPI_LOG_FATAL, plugin_name,
+ "DS_LASRoleDnEval:Syntax error(%s)\n",
+ escape_string_with_punctuation( role, ebuf ),0,0);
+ }
+
+ /* Now we have the starting point of the "roledn" */
+ if ((end_dn = strstr(role, "||")) != NULL) {
+ auto char *t = end_dn;
+ LDAP_UTF8INC(end_dn);
+ LDAP_UTF8INC(end_dn);
+ *t = 0;
+ }
+
+
+ if (*role) {
+ while(ldap_utf8isspace(role))
+ LDAP_UTF8INC(role);
+ /* ignore trailing whitespace */
+ len = strlen(role);
+ ptr = role+len-1;
+ while(ldap_utf8isspace(ptr)) { *ptr = '\0'; LDAP_UTF8DEC(ptr); }
+ }
+
+ /*
+ ** Now we have the DN of the role. Evaluate the "clientdn"
+ ** and see if the user has this role.
+ */
+ if (0 == (strcasecmp(role, "anyone"))) {
+ any_role = 1;
+ }
+
+ if (any_role) {
+ /* anyone in the world */
+ matched = ACL_TRUE;
+ break;
+ } else if ( lasinfo.anomUser &&
+ (lasinfo.aclpb->aclpb_clientcert == NULL) && (!any_role)) {
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "Role not evaluated(%s) for anon user\n", role);
+ break;
+ } else {
+
+ /* Take care of param strings */
+
+ char *s;
+
+ if ((s = strstr (role, ACL_RULE_MACRO_DN_KEY)) != NULL ||
+ (s = strstr (role, ACL_RULE_MACRO_DN_LEVELS_KEY)) != NULL ||
+ (s = strstr (role, ACL_RULE_MACRO_ATTR_KEY)) != NULL) {
+
+ matched = aclutil_evaluate_macro( role, &lasinfo,
+ ACL_EVAL_ROLE);
+ slapi_log_error ( SLAPI_LOG_ACL, plugin_name,
+ "DS_LASRoleDnEval: Param role name:%s\n",
+ role);
+ } else {/* normal evaluation */
+
+ matched = acllas_eval_one_role( role, &lasinfo);
+
+ }
+
+ if ( matched == ACL_TRUE ) {
+ break;
+ } else if ( matched == ACL_DONT_KNOW ) {
+ /* record this but keep going--maybe another role will evaluate to TRUE */
+ got_undefined = 1;
+ }
+ }
+ /* Nothing matched -- try the next DN */
+ role = end_dn;
+
+ } /* end of while */
+
+ /*
+ * If no terms were undefined, then evaluate as normal.
+ * If there was an undefined term, but another one was TRUE, then we also evaluate
+ * as normal. Otherwise, the whole expression is UNDEFINED.
+ */
+ if ( matched == ACL_TRUE || !got_undefined ) {
+ if (comparator == CMP_OP_EQ) {
+ rc = (matched == ACL_TRUE ? LAS_EVAL_TRUE : LAS_EVAL_FALSE);
+ } else {
+ rc = (matched == ACL_TRUE ? LAS_EVAL_FALSE : LAS_EVAL_TRUE);
+ }
+ } else {
+ rc = LAS_EVAL_FAIL;
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "Returning UNDEFINED for roledn evaluation.\n");
+ }
+
+ slapi_ch_free ((void**) &roles);
+ return rc;
+}
+/***************************************************************************
+*
+* DS_LASUserDnAttrEval
+*
+*
+* Input:
+* attr_name The string "userdn" - in lower case.
+* comparator CMP_OP_EQ or CMP_OP_NE only
+* attr_pattern A comma-separated list of users
+* cachable Always set to FALSE.
+* subject Subject property list
+* resource Resource property list
+* auth_info Authentication info, if any
+*
+* Returns:
+* retcode The usual LAS return codes.
+*
+* Error Handling:
+* None.
+*
+**************************************************************************/
+struct userdnattr_info {
+ char *attr;
+ int result;
+ char *clientdn;
+};
+#define ACLLAS_MAX_LEVELS 10
+int
+DS_LASUserDnAttrEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator,
+ char *attr_pattern, int *cachable, void **LAS_cookie,
+ PList_t subject, PList_t resource, PList_t auth_info,
+ PList_t global_auth)
+{
+
+ char *n_currEntryDn = NULL;
+ char *s_attrName, *attrName;
+ char *ptr;
+ int matched;
+ int rc, len, i;
+ char *val;
+ Slapi_Attr *a;
+ int levels[ACLLAS_MAX_LEVELS];
+ int numOflevels =0;
+ struct userdnattr_info info;
+ char *attrs[2] = { LDAP_ALL_USER_ATTRS, NULL };
+ lasInfo lasinfo;
+ int got_undefined = 0;
+
+ if ( 0 != (rc = __acllas_setup (errp, attr_name, comparator,
+ attr_pattern,cachable,LAS_cookie,
+ subject, resource, auth_info,global_auth,
+ DS_LAS_USERDNATTR, "DS_LASUserDnAttrEval",
+ &lasinfo )) ) {
+ return LAS_EVAL_FAIL;
+ }
+
+ /*
+ ** The userdnAttr syntax is
+ ** userdnattr = <attribute> or
+ ** userdnattr = parent[0,2,4].attribute"
+ ** Ex:
+ ** userdnattr = manager; or
+ ** userdnattr = "parent[0,2,4].manager";
+ **
+ ** Here 0 means current level, 2 means grandfather and
+ ** 4 (great great grandfather)
+ **
+ ** The function of this LAS is to compare the value of the
+ ** attribute in the Slapi_Entry with the "userdn".
+ **
+ ** Ex: userdn: "cn=prasanta, o= netscape, c= us"
+ ** and in the Slapi_Entry the manager attribute has
+ ** manager = <value>. Compare the userdn with manager.value to
+ ** determine the result.
+ **
+ */
+ s_attrName = attrName = slapi_ch_strdup (attr_pattern);
+
+ /* ignore leading/trailing whitespace */
+ while(ldap_utf8isspace(attrName)) LDAP_UTF8INC(attrName);
+ len = strlen(attrName);
+ ptr = attrName+len-1;
+ while(ldap_utf8isspace(ptr)) { *ptr = '\0'; LDAP_UTF8DEC(ptr); }
+
+
+ /* See if we have a parent[2].attr" rule */
+ if ( (ptr = strstr(attrName, "parent[")) != NULL) {
+ char *word, *str, *next;
+
+ numOflevels = 0;
+ n_currEntryDn = slapi_entry_get_ndn ( lasinfo.resourceEntry );
+ str = attrName;
+
+ word = ldap_utf8strtok_r(str, "[],. ",&next);
+ /* The first word is "parent[" and so it's not important */
+
+ while ((word= ldap_utf8strtok_r(NULL, "[],.", &next)) != NULL) {
+ if (ldap_utf8isdigit(word)) {
+ while (word && ldap_utf8isspace(word)) LDAP_UTF8INC(word);
+ if (numOflevels < ACLLAS_MAX_LEVELS)
+ levels[numOflevels++] = atoi (word);
+ else {
+ /*
+ * Here, ignore the extra levels..it's really
+ * a syntax error which should have been ruled out at parse time
+ */
+ slapi_log_error( SLAPI_LOG_FATAL, plugin_name,
+ "DS_LASUserDnattr: Exceeded the ATTR LIMIT:%d: Ignoring extra levels\n",
+ ACLLAS_MAX_LEVELS);
+ }
+ } else {
+ /* Must be the attr name. We can goof of by
+ ** having parent[1,2,a] but then you have to be
+ ** stupid to do that.
+ */
+ char *p = word;
+ if (*--p == '.') {
+ attrName = word;
+ break;
+ }
+ }
+ }
+ info.attr = attrName;
+ info.clientdn = lasinfo.clientDn;
+ info.result = 0;
+ } else {
+ levels[0] = 0;
+ numOflevels = 1;
+
+ }
+
+ /* No attribute name specified--it's a syntax error and so undefined */
+ if (attrName == NULL ) {
+ slapi_ch_free ( (void**) &s_attrName);
+ return LAS_EVAL_FAIL;
+ }
+
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,"Attr:%s\n" , attrName, 0,0);
+ matched = ACL_FALSE;
+ for (i=0; i < numOflevels; i++) {
+ if ( levels[i] == 0 ) {
+ Slapi_Value *sval=NULL;
+ const struct berval *attrVal;
+ int j;
+
+ /*
+ * For the add operation, the resource itself (level 0)
+ * must never be allowed to grant access--
+ * This is because access would be granted based on a value
+ * of an attribute in the new entry--security hole.
+ *
+ */
+
+ if ( lasinfo.aclpb->aclpb_optype == SLAPI_OPERATION_ADD) {
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "ACL info: userdnAttr does not allow ADD permission at level 0.\n");
+ got_undefined = 1;
+ continue;
+ }
+ slapi_entry_attr_find( lasinfo.resourceEntry, attrName, &a);
+ if ( NULL == a ) continue;
+ j= slapi_attr_first_value ( a,&sval );
+ while ( j != -1 ) {
+ attrVal = slapi_value_get_berval ( sval );
+ /* Here if atleast 1 value matches then we are done.*/
+ val = slapi_dn_normalize (
+ slapi_ch_strdup( attrVal->bv_val));
+
+ if (slapi_utf8casecmp((ACLUCHP)val, (ACLUCHP)lasinfo.clientDn ) == 0) {
+ char ebuf [ BUFSIZ ];
+ /* Wow it matches */
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "userdnAttr matches(%s, %s) level (%d)\n",
+ val,
+ ACL_ESCAPE_STRING_WITH_PUNCTUATION (lasinfo.clientDn, ebuf),
+ 0);
+ matched = ACL_TRUE;
+ slapi_ch_free ( (void **) &val);
+ break;
+ }
+ slapi_ch_free ( (void**) &val);
+ j = slapi_attr_next_value ( a, j, &sval );
+ }
+ } else {
+ char *p_dn; /* parent dn */
+
+ p_dn = acllas__dn_parent (n_currEntryDn, levels[i]);
+ if (p_dn == NULL) continue;
+
+ /* use new search internal API */
+ {
+ Slapi_PBlock *aPb = slapi_pblock_new ();
+
+ /*
+ * This search may be chained if chaining for ACL is
+ * is enabled in the backend and the entry is in
+ * a chained backend.
+ */
+ slapi_search_internal_set_pb ( aPb,
+ p_dn,
+ LDAP_SCOPE_BASE,
+ "objectclass=*",
+ &attrs[0],
+ 0,
+ NULL /* controls */,
+ NULL /* uniqueid */,
+ aclplugin_get_identity (ACL_PLUGIN_IDENTITY),
+ 0 /* actions */);
+
+ slapi_search_internal_callback_pb(aPb,
+ &info /* callback_data */,
+ NULL/* result_callback */,
+ acllas__verify_client,
+ NULL /* referral_callback */);
+ slapi_pblock_destroy(aPb);
+ }
+
+ /*
+ * Currently info.result is boolean so
+ * we do not need to check for ACL_DONT_KNOW
+ */
+ if (info.result) {
+ matched = ACL_TRUE;
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "userdnAttr matches at level (%d)\n", levels[i]);
+ }
+ }
+ if (matched == ACL_TRUE) {
+ break;
+ }
+ }
+
+ slapi_ch_free ( (void **) &s_attrName);
+
+ /*
+ * If no terms were undefined, then evaluate as normal.
+ * If there was an undefined term, but another one was TRUE, then we also evaluate
+ * as normal. Otherwise, the whole expression is UNDEFINED.
+ */
+ if ( matched == ACL_TRUE || !got_undefined ) {
+ if (comparator == CMP_OP_EQ) {
+ rc = (matched == ACL_TRUE ? LAS_EVAL_TRUE : LAS_EVAL_FALSE);
+ } else {
+ rc = (matched == ACL_TRUE ? LAS_EVAL_FALSE : LAS_EVAL_TRUE);
+ }
+ } else {
+ rc = LAS_EVAL_FAIL;
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "Returning UNDEFINED for userdnattr evaluation.\n");
+ }
+
+ return rc;
+}
+/***************************************************************************
+*
+* DS_LASAuthMethodEval
+*
+*
+* Input:
+* attr_name The string "authmethod" - in lower case.
+* comparator CMP_OP_EQ or CMP_OP_NE only
+* attr_pattern A comma-separated list of users
+* cachable Always set to FALSE.
+* subject Subject property list
+* resource Resource property list
+* auth_info Authentication info, if any
+*
+* Returns:
+* retcode The usual LAS return codes.
+*
+* Error Handling:
+* None.
+*
+**************************************************************************/
+int
+DS_LASAuthMethodEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator,
+ char *attr_pattern, int *cachable, void **LAS_cookie,
+ PList_t subject, PList_t resource, PList_t auth_info,
+ PList_t global_auth)
+{
+
+ char *attr;
+ char *ptr;
+ int len;
+ int matched;
+ int rc;
+ char *s = NULL;
+ lasInfo lasinfo;
+
+ if ( 0 != (rc = __acllas_setup (errp, attr_name, comparator,
+ attr_pattern,cachable,LAS_cookie,
+ subject, resource, auth_info,global_auth,
+ DS_LAS_AUTHMETHOD, "DS_LASAuthMethodEval",
+ &lasinfo )) ) {
+ return LAS_EVAL_FAIL;
+ }
+
+ attr = attr_pattern;
+
+ matched = ACL_FALSE;
+ /* ignore leading whitespace */
+ s = strstr (attr, SLAPD_AUTH_SASL);
+ if ( s) {
+ s +=4;
+ attr = s;
+ }
+
+ while(ldap_utf8isspace(attr)) LDAP_UTF8INC(attr);
+ len = strlen(attr);
+ ptr = attr+len-1;
+ while(ldap_utf8isspace(ptr)) { *ptr = '\0'; LDAP_UTF8DEC(ptr); }
+
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "DS_LASAuthMethodEval:authtype:%s authmethod:%s\n",
+ lasinfo.authType, attr);
+
+ /* None method means, we don't care -- otherwise we care */
+ if ((strcasecmp(attr, "none") == 0) ||
+ (strcasecmp(attr, lasinfo.authType) == 0)) {
+ matched = ACL_TRUE;
+ }
+
+ if ( matched == ACL_TRUE || matched == ACL_FALSE) {
+ if (comparator == CMP_OP_EQ) {
+ rc = (matched == ACL_TRUE ? LAS_EVAL_TRUE : LAS_EVAL_FALSE);
+ } else {
+ rc = (matched == ACL_TRUE ? LAS_EVAL_FALSE : LAS_EVAL_TRUE);
+ }
+ } else {
+ rc = LAS_EVAL_FAIL;
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "Returning UNDEFINED for authmethod evaluation.\n");
+ }
+
+ return rc;
+}
+
+/****************************************************************************
+* Struct to evaluate and keep the current members being evaluated
+*
+* 0 1 2 3 4 5
+* member: [a,b,c,d,e,f]
+* c_idx may point to 2 i.e to "c" if "c" is being evaluated to
+* see if any of "c" members is the clientDN.
+* lu_idx points to the last used spot i.e 5.
+* lu_idx++ is the next free spot.
+*
+* We allocate ACLLAS_MAX_GRP_MEMBER ptr first and then we add if it
+* is required.
+*
+***************************************************************************/
+#define ACLLAS_MAX_GRP_MEMBER 50
+struct member_info
+{
+ char *member; /* member DN */
+ struct member_info *parent; /* parent of this member */
+} member_info;
+
+struct eval_info
+{
+ int result; /* result status */
+ char *userDN; /* client's normalized DN */
+ int c_idx; /* Index to the current member being processed */
+ int lu_idx; /* Index to the slot where the last member is stored */
+ char **member; /* mmebers list */
+ struct member_info **memberInfo;/* array of memberInfo */
+ CERTCertificate *clientCert; /* ptr to cert */
+ struct acl_pblock *aclpb; /*aclpblock */
+} eval_info;
+
+static void
+dump_member_info ( struct member_info *minfo, char *buf )
+{
+ if ( minfo )
+ {
+ if ( minfo->parent )
+ {
+ dump_member_info ( minfo->parent, buf );
+ }
+ else
+ {
+ strcat ( buf, "<nil>" );
+ }
+ strcat ( buf, "->" );
+ strcat ( buf, minfo->member );
+ }
+}
+
+static void
+dump_eval_info (char *caller, struct eval_info *info, int idx)
+{
+ char buf[1024];
+ int len;
+ int i;
+
+ if ( idx < 0 )
+ {
+ sprintf ( buf, "\nuserDN=\"%s\"\nmember=", info->userDN);
+ if (info->member)
+ {
+ len = strlen (buf);
+ sprintf ( &(buf[len]), "\"%s\"", info->member );
+ }
+ len = strlen (buf);
+ sprintf ( &(buf[len]), "\nmemberinfo[%d]-[%d]:", info->c_idx, info->lu_idx );
+ if ( info->memberInfo )
+ for (i = 0; i <= info->lu_idx; i++)
+ {
+ len = strlen(buf);
+ sprintf ( &buf[len], "\n [%d]: ", i );
+ dump_member_info ( info->memberInfo[i], buf );
+ }
+ slapi_log_error ( SLAPI_LOG_FATAL, NULL, "\n======== candidate member info in eval_info ========%s\n\n", buf );
+ }
+ else
+ {
+ sprintf (buf, "evaluated candidate [%d]=", idx);
+ switch (info->result)
+ {
+ case ACL_TRUE:
+ strcat (buf, "ACL_TRUE\n");
+ break;
+ case ACL_FALSE:
+ strcat (buf, "ACL_FALSE\n");
+ break;
+ case ACL_DONT_KNOW:
+ strcat (buf, "ACL_DONT_KNOW\n");
+ break;
+ default:
+ len = strlen (buf);
+ sprintf ( &(buf[len]), "%d\n", info->result );
+ break;
+ }
+ dump_member_info ( info->memberInfo[idx], buf );
+ slapi_log_error ( SLAPI_LOG_FATAL, NULL, "%s\n", buf );
+ }
+}
+
+
+/***************************************************************************
+*
+* acllas__user_ismember_of_group
+*
+* Check if the user is a member of the group and nested groups..
+*
+* Input:
+* char *groupdn - DN of the group
+* char *clientDN - Dn of the client
+*
+* Returns:
+* ACL_TRUE - the user is a member of the group.
+* ACL_FALSE - Not a member
+* ACL_DONT_KNOW - Any errors eg. resource limits exceeded and we could
+* not compelte the evaluation.
+*
+* Error Handling:
+* None.
+*
+**************************************************************************/
+static int
+acllas__user_ismember_of_group( struct acl_pblock *aclpb,
+ char* groupDN,
+ char* clientDN,
+ int cache_status,
+ CERTCertificate *clientCert)
+{
+
+
+ char *attrs[5];
+ char *currDN;
+ int i,j;
+ int result = ACL_FALSE;
+ struct eval_info info;
+ int nesting_level;
+ int numOfMembersAtCurrentLevel;
+ int numOfMembersVisited;
+ int totalMembersVisited;
+ int numOfMembers;
+ int max_nestlevel;
+ int max_memberlimit;
+ aclUserGroup *u_group;
+ char ebuf [ BUFSIZ ];
+ struct member_info *groupMember = NULL;
+ struct member_info *parentGroup = NULL;
+
+ /*
+ ** First, Let's look thru the cached list and determine if the client is
+ ** a member of the cached list of groups.
+ */
+ if ( (u_group = aclg_get_usersGroup ( aclpb , clientDN )) == NULL) {
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "Failed to find/allocate a usergroup--aborting evaluation\n", 0, 0);
+ return(ACL_DONT_KNOW);
+ }
+
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name, "Evaluating user %s in group %s?\n",
+ clientDN, groupDN );
+
+ /* Before I start using, get a reader lock on the group cache */
+ aclg_lock_groupCache ( 1 /* reader */ );
+ for ( i= 0; i < u_group->aclug_numof_member_group; i++) {
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name, "-- In %s\n",
+ u_group->aclug_member_groups[i] );
+ if ( slapi_utf8casecmp((ACLUCHP)groupDN, (ACLUCHP)u_group->aclug_member_groups[i]) == 0){
+ aclg_unlock_groupCache ( 1 /* reader */ );
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name, "Evaluated ACL_TRUE\n");
+ return ACL_TRUE;
+ }
+ }
+
+ /* see if we know the client is not a member of a group. */
+ for ( i= 0; i < u_group->aclug_numof_notmember_group; i++) {
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name, "-- Not in %s\n",
+ u_group->aclug_notmember_groups[i] );
+ if ( slapi_utf8casecmp((ACLUCHP)groupDN, (ACLUCHP)u_group->aclug_notmember_groups[i]) == 0){
+ aclg_unlock_groupCache ( 1 /* reader */ );
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name, "Evaluated ACL_FALSE\n");
+ return ACL_FALSE;
+ }
+ }
+
+ /*
+ ** That means we didn't find the the group in the cache. -- we have to add it
+ ** so no need for READ lock - need to get a WRITE lock. We will get it just before
+ ** modifying it.
+ */
+ aclg_unlock_groupCache ( 1 /* reader */ );
+
+ /* Indicate the initialization handler -- this module will be
+ ** called by the backend to evaluate the entry.
+ */
+ info.result = ACL_FALSE;
+ if (clientDN && *clientDN != '\0')
+ info.userDN = clientDN;
+ else
+ info.userDN = NULL;
+
+ info.c_idx = 0;
+ info.memberInfo = (struct member_info **) slapi_ch_malloc (ACLLAS_MAX_GRP_MEMBER * sizeof(struct member_info *));
+ groupMember = (struct member_info *) slapi_ch_malloc ( sizeof (struct member_info) );
+ groupMember->member = slapi_ch_strdup(groupDN);
+ groupMember->parent = NULL;
+ info.memberInfo[0] = groupMember;
+ info.lu_idx = 0;
+
+ attrs[0] = type_member;
+ attrs[1] = type_uniquemember;
+ attrs[2] = type_memberURL;
+ attrs[3] = type_memberCert;
+ attrs[4] = NULL;
+
+ currDN = groupMember->member;
+
+ /* nesting level is 0 to begin with */
+ nesting_level = 0;
+ numOfMembersVisited = 0;
+ totalMembersVisited = 0;
+ numOfMembersAtCurrentLevel = 1;
+
+ if (clientCert)
+ info.clientCert = clientCert;
+ else
+ info.clientCert = NULL;
+ info.aclpb = aclpb;
+
+ max_memberlimit = aclpb->aclpb_max_member_sizelimit;
+ max_nestlevel = aclpb->aclpb_max_nesting_level;
+
+ /* dump_eval_info ( "acllas__user_ismember_of_group", &info, -1 ); */
+
+eval_another_member:
+
+ numOfMembers = info.lu_idx - info.c_idx;
+
+ /* Use new search internal API */
+ {
+ Slapi_PBlock * aPb = slapi_pblock_new ();
+
+ /*
+ * This search may NOT be chained--we demand that group
+ * definition be local.
+ */
+ slapi_search_internal_set_pb ( aPb,
+ currDN,
+ LDAP_SCOPE_BASE,
+ filter_groups,
+ &attrs[0],
+ 0,
+ NULL /* controls */,
+ NULL /* uniqueid */,
+ aclplugin_get_identity (ACL_PLUGIN_IDENTITY),
+ SLAPI_OP_FLAG_NEVER_CHAIN /* actions */);
+ slapi_search_internal_callback_pb(aPb,
+ &info /* callback_data */,
+ NULL/* result_callback */,
+ acllas__handle_group_entry,
+ NULL /* referral_callback */);
+
+ if ( info.result == ACL_TRUE )
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,"-- In %s\n", info.memberInfo[info.c_idx]->member );
+ else if ( info.result == ACL_FALSE )
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,"-- Not in %s\n", info.memberInfo[info.c_idx]->member );
+
+ slapi_pblock_destroy (aPb);
+ }
+
+ if (info.result == ACL_TRUE) {
+ /*
+ ** that means the client is a member of the
+ ** group or one of the nested groups. We are done.
+ */
+ result = ACL_TRUE;
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name, "Evaluated ACL_TRUE\n");
+ goto free_and_return;
+ }
+ numOfMembersVisited++;
+
+ if (numOfMembersVisited == numOfMembersAtCurrentLevel) {
+ /* This means we have looked at all the members for this level */
+ numOfMembersVisited = 0;
+
+ /* Now we are ready to look at the next level */
+ nesting_level++;
+
+ /* So, far we have visited ... */
+ totalMembersVisited += numOfMembersAtCurrentLevel;
+
+ /* How many members in the next level ? */
+ numOfMembersAtCurrentLevel =
+ info.lu_idx - totalMembersVisited +1;
+ }
+
+ if ((nesting_level > max_nestlevel)) {
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "GroupEval:Member not found within the allowed nesting level (Allowed:%d Looked at:%d)\n",
+ max_nestlevel, nesting_level, 0);
+
+ result = ACL_DONT_KNOW; /* don't try to cache info based on this result */
+ goto free_and_return;
+ }
+
+ /* limit of -1 means "no limit */
+ if (info.c_idx > max_memberlimit &&
+ max_memberlimit != -1 ) {
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "GroupEval:Looked at too many entries:(%d, %d)\n",
+ info.c_idx, info.lu_idx,0);
+ result = ACL_DONT_KNOW; /* don't try to cache info based on this result */
+ goto free_and_return;
+ }
+ if (info.lu_idx > info.c_idx) {
+ if (numOfMembers == (info.lu_idx - info.c_idx)) {
+ /* That means it's not a GROUP. It is just another
+ ** useless member which doesn't match. Remove the BAD dude.
+ */
+ groupMember = info.memberInfo[info.c_idx];
+
+ if (groupMember ) {
+ if ( groupMember->member ) slapi_ch_free ( (void **) &groupMember->member );
+ slapi_ch_free ( (void **) &groupMember );
+ info.memberInfo[info.c_idx] = NULL;
+ }
+ }
+ info.c_idx++;
+
+ /* Go thru the stack and see if we have already
+ ** evaluated this group. If we have, then skip it.
+ */
+ while (1) {
+ int evalNext=0;
+ int j;
+ if (info.c_idx > info.lu_idx) {
+ /* That means we have crossed the limit. We
+ ** may end of in this situation if we
+ ** have circular groups
+ */
+ info.c_idx = info.lu_idx;
+ goto free_and_return;
+ }
+
+ /* Break out of the loop if we have searched to the end */
+ groupMember = info.memberInfo[info.c_idx];
+ if ( (NULL == groupMember) || ((currDN = groupMember->member)!= NULL))
+ break;
+
+ for (j = 0; j < info.c_idx; j++) {
+ groupMember = info.memberInfo[j];
+ if (groupMember->member &&
+ (slapi_utf8casecmp((ACLUCHP)currDN, (ACLUCHP)groupMember->member) == 0)) {
+ /* Don't need the duplicate */
+ groupMember = info.memberInfo[info.c_idx];
+ slapi_ch_free ( (void **) &groupMember->member );
+ slapi_ch_free ( (void **) &groupMember );
+ info.memberInfo[info.c_idx] = NULL;
+ info.c_idx++;
+ evalNext=1;
+ break;
+ }
+ }
+ if (!evalNext) break;
+ }
+ /* Make sure that we have a valid DN to chug along */
+ groupMember = info.memberInfo[info.c_idx];
+ if ((info.c_idx <= info.lu_idx) && ((currDN = groupMember->member) != NULL))
+ goto eval_another_member;
+ }
+
+free_and_return:
+ /* Remove the unnecessary members from the list which
+ ** we might have accumulated during the last execution
+ ** and we don't need to look at them.
+ */
+ i = info.c_idx;
+ i++;
+ while (i <= info.lu_idx) {
+ groupMember = info.memberInfo[i];
+ slapi_ch_free ( (void **) &groupMember->member );
+ slapi_ch_free ( (void **) &groupMember );
+ info.memberInfo[i] = NULL;
+ i++;
+ }
+
+ /*
+ ** Now we have a list which has all the groups
+ ** which we need to cache
+ */
+ info.lu_idx = info.c_idx;
+
+ /* since we are updating the groupcache, get a write lock */
+ aclg_lock_groupCache ( 2 /* writer */ );
+
+ /*
+ ** Keep the result of the evaluation in the cache.
+ ** We have 2 lists: member_of and not_member_of. We can use this
+ ** cached information next time we evaluate groups.
+ */
+ if (result == ACL_TRUE &&
+ (cache_status & ACLLAS_CACHE_MEMBER_GROUPS)) {
+ int ngr = 0;
+
+ /* get the last group which the user is a member of */
+ groupMember = info.memberInfo[info.c_idx];
+
+ while ( groupMember ) {
+ int already_cached = 0;
+
+ parentGroup = groupMember->parent;
+ for (j=0; j < u_group->aclug_numof_member_group;j++){
+ if (slapi_utf8casecmp( (ACLUCHP)groupMember->member,
+ (ACLUCHP)u_group->aclug_member_groups[j]) == 0) {
+ already_cached = 1;
+ break;
+ }
+ }
+ if (already_cached) {
+ groupMember = parentGroup;
+ parentGroup = NULL;
+ continue;
+ }
+
+ ngr = u_group->aclug_numof_member_group++;
+ if (u_group->aclug_numof_member_group >=
+ u_group->aclug_member_group_size){
+ u_group->aclug_member_groups =
+ (char **) slapi_ch_realloc (
+ (void *) u_group->aclug_member_groups,
+ (u_group->aclug_member_group_size +
+ ACLUG_INCR_GROUPS_LIST) *
+ sizeof (char *));
+ u_group->aclug_member_group_size +=
+ ACLUG_INCR_GROUPS_LIST;
+ }
+ u_group->aclug_member_groups[ngr] = slapi_ch_strdup ( groupMember->member );
+ slapi_log_error ( SLAPI_LOG_ACL, plugin_name,
+ "Adding Group (%s) ParentGroup (%s) to the IN GROUP List\n",
+ groupMember->member , parentGroup ? parentGroup->member: "NULL");
+
+ groupMember = parentGroup;
+ parentGroup = NULL;
+ }
+ } else if (result == ACL_FALSE &&
+ (cache_status & ACLLAS_CACHE_NOT_MEMBER_GROUPS)) {
+ int ngr = 0;
+
+ /* NOT IN THE GROUP LIST */
+ /* get the last group which the user is a member of */
+ groupMember = info.memberInfo[info.c_idx];
+
+ while ( groupMember ) {
+ int already_cached = 0;
+
+ parentGroup = groupMember->parent;
+ for (j=0; j < u_group->aclug_numof_notmember_group;j++){
+ if (slapi_utf8casecmp( (ACLUCHP)groupMember->member,
+ (ACLUCHP)u_group->aclug_notmember_groups[j]) == 0) {
+ already_cached = 1;
+ break;
+ }
+ }
+ if (already_cached) {
+ groupMember = parentGroup;
+ parentGroup = NULL;
+ continue;
+ }
+
+ ngr = u_group->aclug_numof_notmember_group++;
+ if (u_group->aclug_numof_notmember_group >=
+ u_group->aclug_notmember_group_size){
+ u_group->aclug_notmember_groups =
+ (char **) slapi_ch_realloc (
+ (void *) u_group->aclug_notmember_groups,
+ (u_group->aclug_notmember_group_size +
+ ACLUG_INCR_GROUPS_LIST) *
+ sizeof (char *));
+ u_group->aclug_notmember_group_size +=
+ ACLUG_INCR_GROUPS_LIST;
+ }
+ u_group->aclug_notmember_groups[ngr] = slapi_ch_strdup ( groupMember->member );
+ slapi_log_error ( SLAPI_LOG_ACL, plugin_name,
+ "Adding Group (%s) ParentGroup (%s) to the NOT IN GROUP List\n",
+ groupMember->member , parentGroup ? parentGroup->member: "NULL");
+
+ groupMember = parentGroup;
+ parentGroup = NULL;
+ }
+ } else if ( result == ACL_DONT_KNOW ) {
+
+ /*
+ * We terminated the search without reaching a conclusion--so
+ * don't cache any info based on this evaluation.
+ */
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name, "Evaluated ACL_DONT_KNOW\n");
+ }
+
+ /* Unlock the group cache, we are done with updating */
+ aclg_unlock_groupCache ( 2 /* writer */ );
+
+ for (i=0; i <= info.lu_idx; i++) {
+ groupMember = info.memberInfo[i];
+ if ( NULL == groupMember ) continue;
+
+ slapi_ch_free ( (void **) &groupMember->member );
+ slapi_ch_free ( (void **) &groupMember );
+ }
+
+ /* free the pointer array.*/
+ slapi_ch_free ( (void **) &info.memberInfo);
+ return result;
+}
+
+/***************************************************************************
+*
+* acllas__handle_group_entry
+*
+* handler called. Compares the userdn value and determines if it's
+* a member of not.
+*
+* Input:
+*
+*
+* Returns:
+*
+* Error Handling:
+*
+**************************************************************************/
+static int
+acllas__handle_group_entry (Slapi_Entry* e, void *callback_data)
+{
+ struct eval_info *info;
+ Slapi_Attr *currAttr, *nextAttr;
+ char *n_dn, *attrType;
+ short n;
+ int i;
+
+ info = (struct eval_info *) callback_data;
+ info->result = ACL_FALSE;
+
+ if (e == NULL) {
+ return 0;
+ }
+
+ slapi_entry_first_attr ( e, &currAttr);
+ if ( NULL == currAttr ) return 0;
+
+ slapi_attr_get_type ( currAttr, &attrType );
+ if (NULL == attrType ) return 0;
+
+ do {
+ Slapi_Value *sval=NULL;
+ const struct berval *attrVal;
+
+ if ((strcasecmp (attrType, type_member) == 0) ||
+ (strcasecmp (attrType, type_uniquemember) == 0 )) {
+
+ i = slapi_attr_first_value ( currAttr,&sval );
+ while ( i != -1 ) {
+ struct member_info *groupMember = NULL;
+ attrVal = slapi_value_get_berval ( sval );
+ n_dn = slapi_dn_normalize ( slapi_ch_strdup( attrVal->bv_val));
+ info->lu_idx++;
+ n = info->lu_idx;
+ if (!(n % ACLLAS_MAX_GRP_MEMBER)) {
+ info->memberInfo = (struct member_info **) slapi_ch_realloc(
+ (void *) info->memberInfo,
+ (n+ACLLAS_MAX_GRP_MEMBER) *
+ sizeof(struct eval_info *));
+ }
+
+ /* allocate the space for the member and attch it to the list */
+ groupMember = (struct member_info *) slapi_ch_malloc ( sizeof ( struct member_info ) );
+ groupMember->member = n_dn;
+ groupMember->parent = info->memberInfo[info->c_idx];
+ info->memberInfo[n] = groupMember;
+
+ if (info->userDN &&
+ slapi_utf8casecmp((ACLUCHP)n_dn, (ACLUCHP)info->userDN) == 0) {
+ info->result = ACL_TRUE;
+ return 0;
+ }
+ i = slapi_attr_next_value ( currAttr, i, &sval );
+ }
+ /* Evaluate Dynamic groups */
+ } else if (strcasecmp ( attrType, type_memberURL) == 0) {
+ char *memberURL, *savURL;
+
+ if (!info->userDN) continue;
+
+ i= slapi_attr_first_value ( currAttr,&sval );
+ while ( i != -1 ) {
+ attrVal = slapi_value_get_berval ( sval );
+ /*
+ * memberURL may start with "ldap:///" or "ldap://host:port"
+ * ldap://localhost:11000/o=ace industry,c=us??
+ * or
+ * ldap:///o=ace industry,c=us??
+ */
+ if (strncasecmp( attrVal->bv_val, "ldap://",7) == 0 ||
+ strncasecmp( attrVal->bv_val, "ldaps://",8) == 0) {
+ savURL = memberURL = slapi_ch_strdup ( attrVal->bv_val);
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "ACL Group Eval:MemberURL:%s\n", memberURL);
+ info->result = acllas__client_match_URL (
+ info->aclpb,
+ info->userDN,
+ memberURL);
+ slapi_ch_free ( (void**) &savURL);
+ if (info->result == ACL_TRUE)
+ return 0;
+ } else {
+ /* This means that the URL is ill-formed */
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "ACL Group Eval:Badly Formed MemberURL:%s\n", attrVal->bv_val);
+ }
+ i = slapi_attr_next_value ( currAttr, i, &sval );
+ }
+ /* Evaluate Fortezza groups */
+ } else if ((strcasecmp (attrType, type_memberCert) == 0) ) {
+ /* Do we have the certificate around */
+ if (!info->clientCert) {
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ " acllas__handle_group_entry:Client Cert missing\n" );
+ continue;
+ }
+ i = slapi_attr_first_value ( currAttr,&sval );
+ while ( i != -1 ) {
+ attrVal = slapi_value_get_berval ( sval );
+ if (ldapu_member_certificate_match (
+ info->clientCert,
+ attrVal->bv_val) == LDAP_SUCCESS) {
+ info->result = ACL_TRUE;
+ return 0;
+ }
+ i = slapi_attr_next_value ( currAttr, i, &sval );
+ }
+ }
+
+ attrType = NULL;
+ /* get the next attr */
+ slapi_entry_next_attr ( e, currAttr, &nextAttr );
+ if ( NULL == nextAttr ) break;
+
+ currAttr = nextAttr;
+ slapi_attr_get_type ( currAttr, &attrType );
+
+ } while ( NULL != attrType );
+
+ return 0;
+}
+/***************************************************************************
+*
+* DS_LASGroupDnAttrEval
+*
+*
+* Input:
+* attr_name The string "groupdnattr" - in lower case.
+* comparator CMP_OP_EQ or CMP_OP_NE only
+* attr_pattern A comma-separated list of users
+* cachable Always set to FALSE.
+* subject Subject property list
+* resource Resource property list
+* auth_info Authentication info, if any
+*
+* Returns:
+* retcode The usual LAS return codes.
+*
+* Error Handling:
+* None.
+*
+**************************************************************************/
+struct groupdnattr_info
+{
+ char *attrName; /* name of the attribute */
+ int numofGroups; /* number of groups */
+ char **member;
+};
+int
+DS_LASGroupDnAttrEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator,
+ char *attr_pattern, int *cachable, void **LAS_cookie,
+ PList_t subject, PList_t resource, PList_t auth_info,
+ PList_t global_auth)
+{
+
+ char *s_attrName = NULL;
+ char *attrName;
+ char *ptr;
+ int matched;
+ int rc;
+ int len;
+ Slapi_Attr *attr;
+ int levels[ACLLAS_MAX_LEVELS];
+ int numOflevels = 0;
+ char *n_currEntryDn = NULL;
+ lasInfo lasinfo;
+ int got_undefined = 0;
+
+ if ( 0 != (rc = __acllas_setup (errp, attr_name, comparator,
+ attr_pattern,cachable,LAS_cookie,
+ subject, resource, auth_info,global_auth,
+ DS_LAS_GROUPDNATTR, "DS_LASGroupDnAttrEval",
+ &lasinfo )) ) {
+ return LAS_EVAL_FAIL;
+ }
+
+ /* For anonymous client, the answer is XXX come back to this */
+ if ( lasinfo.anomUser )
+ return LAS_EVAL_FALSE;
+
+ /*
+ ** The groupdnAttr syntax is
+ ** groupdnattr = <attribute>
+ ** Ex:
+ ** groupdnattr = SIEmanager;
+ **
+ ** The function of this LAS is to find out if the client belongs
+ ** to any group that is specified in the attr.
+ */
+ attrName = attr_pattern;
+ if (strstr(attrName, LDAP_URL_prefix)) {
+ char *s;
+
+
+ /* In this case "grppupdnattr="ldap:///base??attr" */
+
+ if ((s = strstr (attrName, ACL_RULE_MACRO_DN_KEY)) != NULL ||
+ (s = strstr (attrName, ACL_RULE_MACRO_DN_LEVELS_KEY)) != NULL ||
+ (s = strstr (attrName, ACL_RULE_MACRO_ATTR_KEY)) != NULL) {
+
+ matched = aclutil_evaluate_macro( attrName, &lasinfo,
+ ACL_EVAL_GROUPDNATTR);
+ } else{
+
+ matched = acllas__eval_memberGroupDnAttr(attrName,
+ lasinfo.resourceEntry,
+ lasinfo.clientDn,
+ lasinfo.aclpb);
+ }
+ if ( matched == ACL_DONT_KNOW) {
+ got_undefined = 1;
+ }
+ } else {
+ int i;
+ char *n_groupdn;
+
+ /* ignore leading/trailing whitespace */
+ while(ldap_utf8isspace(attrName)) LDAP_UTF8INC(attrName);
+ len = strlen(attrName);
+ ptr = attrName+len-1;
+ while(ldap_utf8isspace(ptr)) { *ptr = '\0'; LDAP_UTF8DEC(ptr); }
+
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,"Attr:%s\n" , attrName, 0,0);
+
+ /* See if we have a parent[2].attr" rule */
+ if ( (ptr = strstr(attrName, "parent[")) != NULL) {
+ char *word, *str, *next;
+
+ numOflevels = 0;
+ n_currEntryDn = slapi_entry_get_ndn ( lasinfo.resourceEntry ) ;
+ s_attrName = attrName = slapi_ch_strdup ( attr_pattern );
+ str = attrName;
+
+ word = ldap_utf8strtok_r(str, "[],. ",&next);
+ /* The first word is "parent[" and so it's not important */
+
+ while ((word= ldap_utf8strtok_r(NULL, "[],.", &next)) != NULL) {
+ if (ldap_utf8isdigit(word)) {
+ while (word && ldap_utf8isspace(word)) LDAP_UTF8INC(word);
+ if (numOflevels < ACLLAS_MAX_LEVELS)
+ levels[numOflevels++] = atoi (word);
+ else {
+ /*
+ * Here, ignore the extra levels..it's really
+ * a syntax error which should have been ruled out at parse time
+ */
+ slapi_log_error( SLAPI_LOG_FATAL, plugin_name,
+ "DS_LASGroupDnattr: Exceeded the ATTR LIMIT:%d: Ignoring extra levels\n",
+ ACLLAS_MAX_LEVELS,0,0);
+ }
+ } else {
+ /* Must be the attr name. We can goof of by
+ ** having parent[1,2,a] but then you have to be
+ ** stupid to do that.
+ */
+ char *p = word;
+ if (*--p == '.') {
+ attrName = word;
+ break;
+ }
+ }
+ }
+ } else {
+ levels[0] = 0;
+ numOflevels = 1;
+ }
+
+ matched = ACL_FALSE;
+ for (i=0; i < numOflevels; i++) {
+ if ( levels[i] == 0 ) {
+ Slapi_Value *sval=NULL;
+ const struct berval *attrVal;
+ int attr_i;
+
+ /*
+ * For the add operation, the resource itself (level 0)
+ * must never be allowed to grant access--
+ * This is because access would be granted based on a value
+ * of an attribute in the new entry--security hole.
+ * XXX is this therefore FALSE or DONT_KNOW ?
+ */
+
+ if ( lasinfo.aclpb->aclpb_optype == SLAPI_OPERATION_ADD) {
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "ACL info: groupdnAttr does not allow ADD permission at level 0.\n");
+ got_undefined = 1;
+ continue;
+ }
+ slapi_entry_attr_find ( lasinfo.resourceEntry, attrName, &attr);
+ if ( !attr) continue;
+ attr_i= slapi_attr_first_value ( attr,&sval );
+ while ( attr_i != -1 ) {
+ attrVal = slapi_value_get_berval ( sval );
+ n_groupdn = slapi_dn_normalize(
+ slapi_ch_strdup( attrVal->bv_val));
+ matched = acllas__user_ismember_of_group (
+ lasinfo.aclpb, n_groupdn, lasinfo.clientDn,
+ ACLLAS_CACHE_MEMBER_GROUPS,
+ lasinfo.aclpb->aclpb_clientcert);
+ slapi_ch_free ( (void **) &n_groupdn);
+ if (matched == ACL_TRUE ) {
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "groupdnattr matches at level (%d)\n", levels[i]);
+ break;
+ } else if ( matched == ACL_DONT_KNOW ) {
+ /* record this but keep going--maybe another group will evaluate to TRUE */
+ got_undefined = 1;
+ }
+ attr_i= slapi_attr_next_value ( attr, attr_i, &sval );
+ }
+ } else {
+ char *p_dn;
+ struct groupdnattr_info info;
+ char *attrs[2];
+ int j;
+
+ info.numofGroups = 0;
+ attrs[0] = info.attrName = attrName;
+ attrs[1] = NULL;
+
+ p_dn = acllas__dn_parent (n_currEntryDn, levels[i]);
+
+ if (p_dn == NULL) continue;
+
+ /* Use new search internal API */
+ {
+
+ Slapi_PBlock *aPb = slapi_pblock_new ();
+ /*
+ * This search may NOT be chained--if the user's definition is
+ * remote and the group is dynamic and the user entry
+ * changes then we would not notice--so don't go
+ * find the user entry in the first place.
+ */
+ slapi_search_internal_set_pb ( aPb,
+ p_dn,
+ LDAP_SCOPE_BASE,
+ "objectclass=*",
+ &attrs[0],
+ 0,
+ NULL /* controls */,
+ NULL /* uniqueid */,
+ aclplugin_get_identity (ACL_PLUGIN_IDENTITY),
+ SLAPI_OP_FLAG_NEVER_CHAIN /* actions */);
+ slapi_search_internal_callback_pb(aPb,
+ &info /* callback_data */,
+ NULL/* result_callback */,
+ acllas__get_members,
+ NULL /* referral_callback */);
+ slapi_pblock_destroy (aPb);
+ }
+
+ if (info.numofGroups <= 0) {
+ continue;
+ }
+ for (j=0; j <info.numofGroups; j++) {
+ if (slapi_utf8casecmp((ACLUCHP)info.member[j],
+ (ACLUCHP)lasinfo.clientDn) == 0) {
+ matched = ACL_TRUE;
+ break;
+ }
+ matched = acllas__user_ismember_of_group (
+ lasinfo.aclpb, info.member[j],
+ lasinfo.clientDn, ACLLAS_CACHE_ALL_GROUPS,
+ lasinfo.aclpb->aclpb_clientcert);
+ if (matched == ACL_TRUE) {
+ break;
+ } else if ( matched == ACL_DONT_KNOW ) {
+ /* record this but keep going--maybe another group will evaluate to TRUE */
+ got_undefined = 1;
+ }
+ }
+ /* Deallocate the member array and the member struct */
+ for (j=0; j < info.numofGroups; j++)
+ slapi_ch_free ((void **) &info.member[j]);
+ slapi_ch_free ((void **) &info.member);
+ }
+ if (matched == ACL_TRUE) {
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "groupdnattr matches at level (%d)\n", levels[i]);
+ break;
+ } else if ( matched == ACL_DONT_KNOW ) {
+ /* record this but keep going--maybe another group at another level
+ * will evaluate to TRUE.
+ */
+ got_undefined = 1;
+ }
+
+ } /* NumofLevels */
+ }
+ if (s_attrName) slapi_ch_free ((void**) &s_attrName );
+
+ /*
+ * If no terms were undefined, then evaluate as normal.
+ * If there was an undefined term, but another one was TRUE, then we also evaluate
+ * as normal. Otherwise, the whole expression is UNDEFINED.
+ */
+ if ( matched == ACL_TRUE || !got_undefined ) {
+ if (comparator == CMP_OP_EQ) {
+ rc = (matched == ACL_TRUE ? LAS_EVAL_TRUE : LAS_EVAL_FALSE);
+ } else {
+ rc = (matched == ACL_TRUE ? LAS_EVAL_FALSE : LAS_EVAL_TRUE);
+ }
+ } else {
+ rc = LAS_EVAL_FAIL;
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "Returning UNDEFINED for groupdnattr evaluation.\n");
+ }
+
+ return rc;
+}
+
+/*
+ * acllas__eval_memberGroupDnAttr
+ *
+ * return ACL_TRUE, ACL_FALSE or ACL_DONT_KNOW
+ *
+ * Inverse group evaluation. Find all the groups that the user is a
+ * member of. Find all teh groups that contain those groups. Do an
+ * upward nested level search. By the end of it, we will know all the
+ * groups that the clinet is a member of under that search scope.
+ *
+ * This model seems to be very fast if we have few groups at the
+ * leaf level.
+ *
+ */
+static int
+acllas__eval_memberGroupDnAttr (char *attrName, Slapi_Entry *e,
+ char *n_clientdn, struct acl_pblock *aclpb)
+{
+
+ Slapi_Attr *attr;
+ char *s, *p;
+ char *str, *s_str, *base, *groupattr;
+ int i,j,k,matched, enumerate_groups;
+ aclUserGroup *u_group;
+ char ebuf [ BUFSIZ ];
+ Slapi_Value *sval=NULL;
+ const struct berval *attrVal;
+
+ /* Parse the URL -- We can't use the ldap_url_parse()
+ ** we don't follow thw complete url naming scheme
+ */
+ s_str = str = slapi_ch_strdup(attrName);
+ while (str && ldap_utf8isspace(str)) LDAP_UTF8INC( str );
+ str +=8;
+ s = strchr (str, '?');
+ if (s) {
+ p = s;
+ p++;
+ *s = '\0';
+ base = str;
+ s = strchr (p, '?');
+ if (s) *s = '\0';
+
+ groupattr = p;
+ } else {
+ slapi_ch_free ( (void **)&s_str );
+ return ACL_FALSE;
+ }
+
+ if ( (u_group = aclg_get_usersGroup ( aclpb , n_clientdn )) == NULL) {
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "Failed to find/allocate a usergroup--aborting evaluation\n", 0, 0);
+ slapi_ch_free ( (void **)&s_str );
+ return(ACL_DONT_KNOW);
+ }
+
+ /*
+ ** First find out if we have already searched this base or
+ ** if we are searching a subtree to an already enumerated base.
+ */
+ enumerate_groups = 1;
+ for (j=0; j < aclpb->aclpb_numof_bases; j++) {
+ if (slapi_dn_issuffix(aclpb->aclpb_grpsearchbase[j], base)) {
+ enumerate_groups = 0;
+ break;
+ }
+ }
+
+
+ /* See if we have already enumerated all the groups which the
+ ** client is a member of.
+ */
+ if (enumerate_groups) {
+ char filter_str[BUFSIZ];
+ char *attrs[3];
+ struct eval_info info;
+ char *curMemberDn;
+ int Done = 0;
+ int ngr, tt;
+
+ /* Add the scope to the list of scopes */
+ if (aclpb->aclpb_numof_bases >= (aclpb->aclpb_grpsearchbase_size-1)) {
+ aclpb->aclpb_grpsearchbase = (char **)
+ slapi_ch_realloc (
+ (void *) aclpb->aclpb_grpsearchbase,
+ (aclpb->aclpb_grpsearchbase_size +
+ ACLPB_INCR_BASES) *
+ sizeof (char *));
+ aclpb->aclpb_grpsearchbase_size += ACLPB_INCR_BASES;
+ }
+ aclpb->aclpb_grpsearchbase[aclpb->aclpb_numof_bases++] =
+ slapi_dn_normalize(slapi_ch_strdup(base));
+
+ /* Set up info to do a search */
+ attrs[0] = type_member;
+ attrs[1] = type_uniquemember;
+ attrs[2] = NULL;
+
+ info.c_idx = info.lu_idx = 0;
+ info.member =
+ (char **) slapi_ch_malloc (ACLLAS_MAX_GRP_MEMBER * sizeof(char *));
+ curMemberDn = n_clientdn;
+
+ while (!Done) {
+ char *filter_str_ptr = &filter_str[0];
+ char *new_filter_str = NULL;
+ int lenf = strlen(curMemberDn)<<1;
+
+ if (lenf > (BUFSIZ - 28)) { /* 28 for "(|(uniquemember=%s)(member=%s))" */
+ new_filter_str = slapi_ch_malloc(lenf + 28);
+ filter_str_ptr = new_filter_str;
+ }
+
+ /*
+ ** Search the db for groups that the client is a member of.
+ ** Once found cache it. cache only unique groups.
+ */
+ tt = info.lu_idx;
+ sprintf (filter_str_ptr,"(|(uniquemember=%s)(member=%s))",
+ curMemberDn, curMemberDn);
+
+ /* Use new search internal API */
+ {
+ Slapi_PBlock *aPb = slapi_pblock_new ();
+ /*
+ * This search may NOT be chained--we demand that group
+ * definition be local.
+ */
+ slapi_search_internal_set_pb ( aPb,
+ base,
+ LDAP_SCOPE_SUBTREE,
+ filter_str_ptr,
+ &attrs[0],
+ 0,
+ NULL /* controls */,
+ NULL /* uniqueid */,
+ aclplugin_get_identity (ACL_PLUGIN_IDENTITY),
+ SLAPI_OP_FLAG_NEVER_CHAIN /* actions */);
+ slapi_search_internal_callback_pb(aPb,
+ &info /* callback_data */,
+ NULL/* result_callback */,
+ acllas__add_allgroups,
+ NULL /* referral_callback */);
+ slapi_pblock_destroy (aPb);
+ }
+
+ if (new_filter_str) slapi_ch_free((void **) &new_filter_str);
+
+ if (tt == info.lu_idx) {
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name, "currDn:(%s) \n\tNO MEMBER ADDED\n",
+ ACL_ESCAPE_STRING_WITH_PUNCTUATION (curMemberDn, ebuf) , 0,0);
+ } else {
+ for (i=tt; i < info.lu_idx; i++)
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "currDn:(%s) \n\tADDED MEMBER[%d]=%s\n",
+ ACL_ESCAPE_STRING_WITH_PUNCTUATION (curMemberDn, ebuf), i, info.member[i]);
+ }
+
+ if (info.c_idx >= info.lu_idx) {
+ for (i=0; i < info.lu_idx; i++) {
+ int already_cached = 0;
+ for (j=0; j < u_group->aclug_numof_member_group;
+ j++){
+ if (slapi_utf8casecmp(
+ (ACLUCHP)info.member[i],
+ (ACLUCHP)u_group->aclug_member_groups[j]) == 0) {
+ slapi_ch_free ((void **) &info.member[i] );
+ info.member[i] = NULL;
+ already_cached = 1;
+ break;
+ }
+
+ }
+
+ if (already_cached) continue;
+
+ ngr = u_group->aclug_numof_member_group++;
+ if (u_group->aclug_numof_member_group >=
+ u_group->aclug_member_group_size){
+ u_group->aclug_member_groups =
+ (char **) slapi_ch_realloc (
+ (void *) u_group->aclug_member_groups,
+ (u_group->aclug_member_group_size +
+ ACLUG_INCR_GROUPS_LIST) * sizeof(char *));
+
+ u_group->aclug_member_group_size +=
+ ACLUG_INCR_GROUPS_LIST;
+ }
+ u_group->aclug_member_groups[ngr] = info.member[i];
+ info.member[i] = NULL;
+ }
+ slapi_ch_free ((void **) &info.member);
+ Done = 1;
+ } else {
+ curMemberDn = info.member[info.c_idx];
+ info.c_idx++;
+ }
+ }
+ }
+
+ for (j=0; j < u_group->aclug_numof_member_group; j++)
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "acllas__eval_memberGroupDnAttr:GROUP[%d] IN CACHE:%s\n",
+ j, ACL_ESCAPE_STRING_WITH_PUNCTUATION (u_group->aclug_member_groups[j], ebuf),0);
+
+ matched = ACL_FALSE;
+ slapi_entry_attr_find( e, groupattr, &attr);
+ if (attr == NULL) {
+ slapi_ch_free ( (void **)&s_str );
+ return ACL_FALSE;
+ }
+ {
+ k = slapi_attr_first_value ( attr,&sval );
+ while ( k != -1 ) {
+ char *n_attrval;
+ attrVal = slapi_value_get_berval ( sval );
+ n_attrval = slapi_ch_strdup( attrVal->bv_val);
+ n_attrval = slapi_dn_normalize (n_attrval);
+
+ /* We support: The attribute value can be a USER or a GROUP.
+ ** Let's compare with the client, thi might be just an user. If it is not
+ ** then we test it against the list of groups.
+ */
+ if (slapi_utf8casecmp ((ACLUCHP)n_attrval, (ACLUCHP)n_clientdn) == 0 ) {
+ matched = ACL_TRUE;
+ slapi_ch_free ( (void **)&n_attrval );
+ break;
+ }
+ for (j=0; j <u_group->aclug_numof_member_group; j++) {
+ if ( slapi_utf8casecmp((ACLUCHP)n_attrval,
+ (ACLUCHP)u_group->aclug_member_groups[j]) == 0) {
+ matched = ACL_TRUE;
+ break;
+ }
+ }
+ slapi_ch_free ( (void **)&n_attrval );
+ if (matched == ACL_TRUE) break;
+ k= slapi_attr_next_value ( attr, k, &sval );
+ }
+ }
+ slapi_ch_free ( (void **)&s_str );
+ return matched;
+}
+
+static int
+acllas__add_allgroups (Slapi_Entry* e, void *callback_data)
+{
+ int i, n, m;
+ struct eval_info *info;
+ char *n_dn;
+
+ info = (struct eval_info *) callback_data;
+
+ /*
+ ** Once we are here means this is a valid group. First see
+ ** If we have already seen this group. If not, add it to the
+ ** member list.
+ */
+ n_dn = slapi_ch_strdup ( slapi_entry_get_ndn ( e ) );
+ for (i=0; i < info->lu_idx; i++) {
+ if (slapi_utf8casecmp((ACLUCHP)n_dn, (ACLUCHP)info->member[i]) == 0) {
+ slapi_ch_free ( (void **) &n_dn);
+ return 0;
+ }
+ }
+
+ m = info->lu_idx;
+ n = info->lu_idx++;
+ if (!(n % ACLLAS_MAX_GRP_MEMBER)) {
+ info->member = (char **) slapi_ch_realloc (
+ (void *) info->member,
+ (n+ACLLAS_MAX_GRP_MEMBER) * sizeof(char *));
+ }
+ info->member[m] = n_dn;
+ return 0;
+}
+/*
+ *
+ * acllas__dn_parent
+ *
+ * This code should belong to dn.c. However this is specific to acl and I had
+ * 2 choices 1) create a new API or 2) reuse the slapi_dN_parent
+ *
+ * Returns a ptr to the parent based on the level.
+ *
+ */
+#define DNSEPARATOR(c) (c == ',' || c == ';')
+static char*
+acllas__dn_parent( char *dn, int level)
+{
+ char *s, *dnstr;
+ int inquote;
+ int curLevel;
+ int lastLoop = 0;
+
+ if ( dn == NULL || *dn == '\0' ) {
+ return( NULL );
+ }
+
+ /* An X.500-style name, which looks like foo=bar,sha=baz,... */
+ /* Do we have any dn seprator or not */
+ if ((strchr(dn,',') == NULL) && (strchr(dn,';') == NULL))
+ return (NULL);
+
+ inquote = 0;
+ curLevel = 1;
+ dnstr = dn;
+ while ( curLevel <= level) {
+ if (lastLoop) break;
+ if (curLevel == level) lastLoop = 1;
+ for ( s = dnstr; *s; s++ ) {
+ if ( *s == '\\' ) {
+ if ( *(s + 1) )
+ s++;
+ continue;
+ }
+ if ( inquote ) {
+ if ( *s == '"' )
+ inquote = 0;
+ } else {
+ if ( *s == '"' )
+ inquote = 1;
+ else if ( DNSEPARATOR( *s ) ) {
+ if (curLevel == level)
+ return( s + 1 );
+ dnstr = s + 1;
+ curLevel++;
+ break;
+ }
+ }
+ }
+ if ( *s == '\0') {
+ /* Got to the end of the string without reaching level,
+ * so return NULL.
+ */
+ return(NULL);
+ }
+ }
+
+ return( NULL );
+}
+/*
+ * acllas__verify_client
+ *
+ * returns 1 if the attribute exists in the entry and
+ * it's value is equal to the client Dn.
+ * If the attribute is not in the entry, or it is and the
+ * value differs from the clientDn then returns FALSE.
+ *
+ * Verify if client's DN is stored in the attrbute or not.
+ * This is a handler from a search being done at
+ * DS_LASUserDnAttrEval().
+ *
+ */
+static int
+acllas__verify_client (Slapi_Entry* e, void *callback_data)
+{
+
+ Slapi_Attr *attr;
+ char *val;
+ struct userdnattr_info *info;
+ Slapi_Value *sval;
+ const struct berval *attrVal;
+ int i;
+
+ info = (struct userdnattr_info *) callback_data;
+
+ slapi_entry_attr_find( e, info->attr, &attr);
+ if (attr == NULL) return 0;
+
+ i = slapi_attr_first_value ( attr,&sval );
+ while ( i != -1 ) {
+ attrVal = slapi_value_get_berval ( sval );
+ val = slapi_dn_normalize (
+ slapi_ch_strdup(attrVal->bv_val));
+
+ if (slapi_utf8casecmp((ACLUCHP)val, (ACLUCHP)info->clientdn ) == 0) {
+ info->result = 1;
+ slapi_ch_free ( (void **) &val);
+ return 0;
+ }
+ slapi_ch_free ( (void **) &val);
+ i = slapi_attr_next_value ( attr, i, &sval );
+ }
+ return 0;
+}
+/*
+ *
+ * acllas__get_members
+ *
+ * Collects all the values of the specified attribute which should be group names.
+ */
+static int
+acllas__get_members (Slapi_Entry* e, void *callback_data)
+{
+
+ Slapi_Attr *attr;
+ struct groupdnattr_info *info;
+ Slapi_Value *sval=NULL;
+ const struct berval *attrVal;
+ int i;
+
+ info = (struct groupdnattr_info *) callback_data;
+ slapi_entry_attr_find (e, info->attrName, &attr);
+ if ( !attr ) return 0;
+
+ slapi_attr_get_numvalues ( attr, &info->numofGroups );
+
+ info->member = (char **) slapi_ch_malloc (info->numofGroups * sizeof(char *));
+ i = slapi_attr_first_value ( attr,&sval );
+ while ( i != -1 ) {
+ attrVal =slapi_value_get_berval ( sval );
+ info->member[i] = slapi_dn_normalize ( slapi_ch_strdup(attrVal->bv_val));
+ i = slapi_attr_next_value ( attr, i, &sval );
+ }
+ return 0;
+}
+
+/*
+ * DS_LASUserAttrEval
+ * LAS to evaluate the userattr rule
+ *
+ * userAttr = "attrName#Type"
+ *
+ * <Type> ::= "USERDN" | "GROUPDN" | "ROLEDN" | "LDAPURL" | <value>
+ * <value>::== <any printable String>
+ *
+ * Example:
+ * userAttr = "manager#USERDN" --- same as userdnattr
+ * userAttr = "owner#GROUPDN" --- same as groupdnattr
+ * = "ldap:///o=sun.com?owner#GROUPDN
+ * userAttr = "attr#ROLEDN" --- The value of attr contains a roledn
+ * userAttr = "myattr#LDAPURL" --- The value contains a LDAP URL
+ * which can have scope and filter
+ * bits.
+ * userAttr = "OU#Directory Server"
+ * --- In this case the client's OU and the
+ * resource entry's OU must have
+ * "Directory Server" value.
+ *
+ * Returns:
+ * retcode The usual LAS return codes.
+ */
+int
+DS_LASUserAttrEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator,
+ char *attr_pattern, int *cachable, void **LAS_cookie,
+ PList_t subject, PList_t resource, PList_t auth_info,
+ PList_t global_auth)
+{
+
+ char *attrName;
+ char *attrValue = NULL;
+ int rc;
+ int matched = ACL_FALSE;
+ char *p;
+ int URLAttrRule = 0;
+ lasInfo lasinfo;
+ int got_undefined = 0;
+
+ if ( 0 != (rc = __acllas_setup (errp, attr_name, comparator,
+ attr_pattern,cachable,LAS_cookie,
+ subject, resource, auth_info,global_auth,
+ DS_LAS_USERATTR, "DS_LASUserAttrEval",
+ &lasinfo )) ) {
+ return LAS_EVAL_FAIL;
+ }
+
+ /* Which rule are we evaluating ? */
+ attrName = slapi_ch_strdup (attr_pattern );
+ if ( NULL == (p = strchr ( attrName, '#' ))) {
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "DS_LASUserAttrEval:Invalid value(%s)\n", attr_pattern);
+ slapi_ch_free ( (void **) &attrName );
+ return LAS_EVAL_FAIL;
+ }
+ attrValue = p;
+ attrValue++; /* skip the # */
+ *p = '\0'; /* null terminate the attr name */
+
+ if ( 0 == strncasecmp ( attrValue, "USERDN", 6)) {
+ matched = DS_LASUserDnAttrEval (errp,DS_LAS_USERDNATTR, comparator,
+ attrName, cachable, LAS_cookie,
+ subject, resource, auth_info, global_auth);
+ goto done_las;
+ } else if ( 0 == strncasecmp ( attrValue, "GROUPDN", 7)) {
+ matched = DS_LASGroupDnAttrEval (errp,DS_LAS_GROUPDNATTR, comparator,
+ attrName, cachable, LAS_cookie,
+ subject, resource, auth_info, global_auth);
+ goto done_las;
+ } else if ( 0 == strncasecmp ( attrValue, "LDAPURL", 7) ) {
+ URLAttrRule = 1;
+ } else if ( 0 == strncasecmp ( attrValue, "ROLEDN", 6)) {
+ matched = DS_LASRoleDnAttrEval (errp,DS_LAS_ROLEDN, comparator,
+ attrName, cachable, LAS_cookie,
+ subject, resource, auth_info, global_auth);
+ goto done_las;
+ }
+
+ if ( lasinfo.aclpb && ( NULL == lasinfo.aclpb->aclpb_client_entry )) {
+ /* SD 00/16/03 pass NULL in case the req is chained */
+ char **attrs=NULL;
+
+
+ /* Use new search internal API */
+ Slapi_PBlock *aPb = slapi_pblock_new ();
+ /*
+ * This search may be chained if chaining for ACL is
+ * is enabled in the backend and the entry is in
+ * a chained backend.
+ */
+ slapi_search_internal_set_pb ( aPb,
+ lasinfo.clientDn,
+ LDAP_SCOPE_BASE,
+ "objectclass=*",
+ attrs,
+ 0,
+ NULL /* controls */,
+ NULL /* uniqueid */,
+ aclplugin_get_identity (ACL_PLUGIN_IDENTITY),
+ 0 /* actions */);
+ slapi_search_internal_callback_pb(aPb,
+ lasinfo.aclpb /* callback_data */,
+ NULL/* result_callback */,
+ acllas__handle_client_search,
+ NULL /* referral_callback */);
+ slapi_pblock_destroy (aPb);
+
+ }
+
+ slapi_log_error ( SLAPI_LOG_ACL, plugin_name,
+ "DS_LASUserAttrEval: AttrName:%s, attrVal:%s\n", attrName, attrValue );
+
+ if ( URLAttrRule ) {
+ Slapi_Value *sval=NULL;
+ const struct berval *attrVal;
+ Slapi_Attr *attrs;
+ int i;
+
+ /* Get the attr from the resouce entry */
+ if ( 0 == slapi_entry_attr_find (lasinfo.resourceEntry, attrName, &attrs) ) {
+ i= slapi_attr_first_value ( attrs, &sval );
+ if ( i==-1 ) {
+ matched = ACL_FALSE; /* Attr val not there so it's value cannot equal other one */
+ goto done_acl;
+ }
+ } else {
+ matched = ACL_FALSE; /* Not there so it cannot equal another one */
+ goto done_acl;
+ }
+
+ while( matched != ACL_TRUE && (sval != NULL)) {
+ attrVal = slapi_value_get_berval ( sval );
+ matched = acllas__client_match_URL ( lasinfo.aclpb,
+ lasinfo.clientDn,
+ attrVal->bv_val);
+ if ( matched != ACL_TRUE )
+ i = slapi_attr_next_value ( attrs, i, &sval );
+ if ( matched == ACL_DONT_KNOW ) {
+ got_undefined = 1;
+ }
+ }/* while */
+ } else {
+ /*
+ * Here it's the userAttr = "OU#Directory Server" case.
+ * Allocate the Slapi_Value on the stack and init it by reference
+ * to avoid having to malloc and free memory.
+ */
+ Slapi_Value v;
+
+ slapi_value_init_string_passin(&v, attrValue);
+ rc = slapi_entry_attr_has_syntax_value ( lasinfo.resourceEntry, attrName,
+ &v );
+ if (rc) {
+ rc = slapi_entry_attr_has_syntax_value (
+ lasinfo.aclpb->aclpb_client_entry,
+ attrName, &v );
+ if (rc) matched = ACL_TRUE;
+ }
+ /* Nothing to free--cool */
+ }
+
+ /*
+ * Find out what the result is, in
+ * this case matched is one of ACL_TRUE, ACL_FALSE or ACL_DONT_KNOW
+ * and got_undefined says whether a logical term evaluated to ACL_DONT_KNOW.
+ *
+ */
+done_acl:
+ if ( matched == ACL_TRUE || !got_undefined) {
+ if (comparator == CMP_OP_EQ) {
+ rc = (matched == ACL_TRUE ? LAS_EVAL_TRUE : LAS_EVAL_FALSE);
+ } else {
+ rc = (matched == ACL_TRUE ? LAS_EVAL_FALSE : LAS_EVAL_TRUE);
+ }
+ } else {
+ rc = LAS_EVAL_FAIL;
+ }
+
+ slapi_ch_free ( (void **) &attrName );
+ return rc;
+
+done_las:
+ /*
+ * In this case matched is already LAS_EVAL_TRUE or LAS_EVAL_FALSE or
+ * LAS_EVAL_FAIL.
+ */
+ if ( matched != LAS_EVAL_FAIL ) {
+ if (comparator == CMP_OP_EQ) {
+ rc = matched;
+ } else {
+ rc = (matched == LAS_EVAL_TRUE ? LAS_EVAL_FALSE : LAS_EVAL_TRUE);
+ }
+ }
+
+ slapi_ch_free ( (void **) &attrName );
+ return rc;
+}
+
+/*
+ * acllas__client_match_URL
+ * Match a client to a URL.
+ *
+ * Returns:
+ * ACL_TRUE - matched the URL
+ * ACL_FALSE - Sorry; no match
+ *
+ */
+static int
+acllas__client_match_URL (struct acl_pblock *aclpb, char *n_clientdn, char *url )
+{
+
+ LDAPURLDesc *ludp;
+ int rc;
+ Slapi_Filter *f = NULL;
+
+
+ /* Get the client's entry if we don't have already */
+ if ( aclpb && ( NULL == aclpb->aclpb_client_entry )) {
+ /* SD 00/16/03 Get every attr in case req chained */
+ char **attrs=NULL;
+
+ /* Use new search internal API */
+ Slapi_PBlock * aPb = slapi_pblock_new ();
+ /*
+ * This search may be chained if chaining for ACL is
+ * is enabled in the backend and the entry is in
+ * a chained backend.
+ */
+ slapi_search_internal_set_pb ( aPb,
+ n_clientdn,
+ LDAP_SCOPE_BASE,
+ "objectclass=*",
+ attrs,
+ 0,
+ NULL /* controls */,
+ NULL /* uniqueid */,
+ aclplugin_get_identity (ACL_PLUGIN_IDENTITY),
+ 0 /* actions */);
+ slapi_search_internal_callback_pb(aPb,
+ aclpb /* callback_data */,
+ NULL/* result_callback */,
+ acllas__handle_client_search,
+ NULL /* referral_callback */);
+ slapi_pblock_destroy (aPb);
+ }
+
+ if ( NULL == aclpb->aclpb_client_entry ) {
+ slapi_log_error ( SLAPI_LOG_ACL, plugin_name,
+ "DS_LASUserAttrEval: Unable to get client's entry\n",0,0,0);
+ return ACL_FALSE;
+ }
+
+ if (( rc = ldap_url_parse( url, &ludp)) != 0 ) {
+ return ACL_FALSE;
+
+ }
+ if ( ( NULL == ludp->lud_dn) || ( NULL == ludp->lud_filter) ) {
+ ldap_free_urldesc( ludp );
+ return ACL_FALSE;
+ }
+
+ /* Normalize in place the dn */
+ slapi_dn_normalize ( ludp->lud_dn );
+
+ /* Check the scope */
+ if ( ludp->lud_scope == LDAP_SCOPE_SUBTREE ) {
+ if (!slapi_dn_issuffix(n_clientdn, ludp->lud_dn)) {
+ ldap_free_urldesc( ludp );
+ return ACL_FALSE;
+ }
+ } else if ( ludp->lud_scope == LDAP_SCOPE_ONELEVEL ) {
+ char *parent = slapi_dn_parent (n_clientdn);
+
+ if (slapi_utf8casecmp ((ACLUCHP)parent, (ACLUCHP)ludp->lud_dn) != 0 ) {
+ slapi_ch_free ( (void **) &parent);
+ ldap_free_urldesc( ludp );
+ return ACL_FALSE;
+ }
+ slapi_ch_free ( (void **) &parent);
+ } else { /* default */
+ if (slapi_utf8casecmp ( (ACLUCHP)n_clientdn, (ACLUCHP)ludp->lud_dn) != 0 ) {
+ ldap_free_urldesc( ludp );
+ return ACL_FALSE;
+ }
+
+ }
+
+
+ /* Convert the filter string */
+ f = slapi_str2filter ( ludp->lud_filter );
+
+ rc = ACL_TRUE;
+ if (0 != slapi_vattr_filter_test ( aclpb->aclpb_pblock,
+ aclpb->aclpb_client_entry, f, 0 /* no acces chk */ ))
+ rc = ACL_FALSE;
+
+ ldap_free_urldesc( ludp );
+ slapi_filter_free ( f, 1 ) ;
+
+ return rc;
+}
+static int
+acllas__handle_client_search ( Slapi_Entry *e, void *callback_data )
+{
+ struct acl_pblock *aclpb = (struct acl_pblock *) callback_data;
+
+ /* If we are here means we have found the entry */
+ if ( NULL == aclpb-> aclpb_client_entry)
+ aclpb->aclpb_client_entry = slapi_entry_dup ( e );
+ return 0;
+}
+/*
+*
+* Do all the necessary setup for all the
+* LASes.
+* It will only fail if it's passed garbage (which should not happen) or
+* if the data it needs to stock the lasinfo is not available, which
+* also should not happen.
+*
+*
+* Return value: 0 or one of these
+* #define LAS_EVAL_TRUE -1
+* #define LAS_EVAL_FALSE -2
+* #define LAS_EVAL_DECLINE -3
+* #define LAS_EVAL_FAIL -4
+* #define LAS_EVAL_INVALID -5
+*/
+
+static int
+__acllas_setup ( NSErr_t *errp, char *attr_name, CmpOp_t comparator,
+ char *attr_pattern, int *cachable, void **LAS_cookie,
+ PList_t subject, PList_t resource, PList_t auth_info,
+ PList_t global_auth, char *lasType, char*lasName, lasInfo *linfo)
+{
+
+ int rc;
+ memset ( linfo, 0, sizeof ( lasInfo) );
+
+ *cachable = 0;
+ *LAS_cookie = (void *)0;
+
+ if (strcmp(attr_name, lasType) != 0) {
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "%s:Invalid LAS(%s)\n", lasName, attr_name);
+ return LAS_EVAL_INVALID;
+ }
+
+ if ((comparator != CMP_OP_EQ) && (comparator != CMP_OP_NE)) {
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "%s:Invalid comparator(%d)\n", lasName, (int)comparator);
+ return LAS_EVAL_INVALID;
+ }
+
+
+ /* Get the client DN */
+ rc = ACL_GetAttribute(errp, DS_ATTR_USERDN, (void **)&linfo->clientDn,
+ subject, resource, auth_info, global_auth);
+
+ if ( rc != LAS_EVAL_TRUE ) {
+ acl_print_acllib_err(errp, NULL);
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "%s:Unable to get the clientdn attribute(%d)\n",lasName, rc);
+ return LAS_EVAL_FAIL;
+ }
+
+ /* Check if we have a user or not */
+ if (linfo->clientDn) {
+ /* See if it's a anonymous user */
+ if (*(linfo->clientDn) == '\0')
+ linfo->anomUser = ACL_TRUE;
+ } else {
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "%s: No user\n",lasName);
+ return LAS_EVAL_FAIL;
+ }
+
+ if ((rc = PListFindValue(subject, DS_ATTR_ENTRY,
+ (void **)&linfo->resourceEntry, NULL)) < 0) {
+ acl_print_acllib_err(errp, NULL);
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "%s:Unable to get the Slapi_Entry attr(%d)\n",lasName, rc);
+ return LAS_EVAL_FAIL;
+ }
+
+ /* Get ACLPB */
+ rc = ACL_GetAttribute(errp, DS_PROP_ACLPB, (void **)&linfo->aclpb,
+ subject, resource, auth_info, global_auth);
+ if ( rc != LAS_EVAL_TRUE ) {
+ acl_print_acllib_err(errp, NULL);
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "%s:Unable to get the ACLPB(%d)\n", lasName, rc);
+ return LAS_EVAL_FAIL;
+ }
+ if (NULL == attr_pattern ) {
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "%s:No rule value in the ACL\n", lasName);
+
+ return LAS_EVAL_FAIL;
+ }
+ /* get the authentication type */
+ if ((rc = PListFindValue(subject, DS_ATTR_AUTHTYPE,
+ (void **)&linfo->authType, NULL)) < 0) {
+ acl_print_acllib_err(errp, NULL);
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "%s:Unable to get the auth type(%d)\n", rc);
+ return LAS_EVAL_FAIL;
+ }
+ return 0;
+}
+
+/*
+ * See if clientDN has role roleDN.
+ * Here we know the user is not anon and that the role
+ * is not the anyone role ie. it's actually worth invoking the roles code.
+*/
+
+static int acllas__user_has_role( struct acl_pblock *aclpb,
+ Slapi_DN *roleDN, Slapi_DN *clientDn) {
+
+ int present = 0;
+ int rc = 0;
+
+ /* Get the client's entry if we don't have already */
+ if ( aclpb && ( NULL == aclpb->aclpb_client_entry )) {
+ /* SD 00/16/03 Get every attr in case req chained */
+ char **attrs=NULL;
+
+ /* Use new search internal API */
+ Slapi_PBlock * aPb = slapi_pblock_new ();
+ /*
+ * This search may NOT be chained--the user and the role definition
+ * must be co-located (chaining is not supported for the roles
+ * plugin in 5.0
+ */
+ slapi_search_internal_set_pb ( aPb,
+ slapi_sdn_get_ndn(clientDn),
+ LDAP_SCOPE_BASE,
+ "objectclass=*",
+ attrs,
+ 0,
+ NULL /* controls */,
+ NULL /* uniqueid */,
+ aclplugin_get_identity (ACL_PLUGIN_IDENTITY),
+ SLAPI_OP_FLAG_NEVER_CHAIN /* actions */);
+ slapi_search_internal_callback_pb(aPb,
+ aclpb /* callback_data */,
+ NULL/* result_callback */,
+ acllas__handle_client_search,
+ NULL /* referral_callback */);
+ slapi_pblock_destroy (aPb);
+ }
+
+ if ( NULL == aclpb->aclpb_client_entry ) {
+ slapi_log_error ( SLAPI_LOG_ACL, plugin_name,
+ "acllas__user_has_role: Unable to get client's entry\n",0,0,0);
+ return ACL_FALSE;
+ }
+
+ /* If the client has the role then it's a match, otherwise no */
+
+ rc = slapi_role_check( aclpb->aclpb_client_entry, roleDN, &present);
+ if ( present ) {
+ return(ACL_TRUE);
+ }
+
+ return(ACL_FALSE);
+}
+
+int
+DS_LASRoleDnAttrEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator,
+ char *attr_pattern, int *cachable, void **LAS_cookie,
+ PList_t subject, PList_t resource, PList_t auth_info,
+ PList_t global_auth)
+{
+
+ char *s_attrName = NULL;
+ char *attrName;
+ int matched;
+ int rc;
+ Slapi_Attr *attr;
+ int numOflevels = 0;
+ char *n_currEntryDn = NULL;
+ lasInfo lasinfo;
+ Slapi_Value *sval=NULL;
+ const struct berval *attrVal;
+ int k=0;
+ int got_undefined = 0;
+
+ if ( 0 != (rc = __acllas_setup (errp, attr_name, comparator,
+ attr_pattern,cachable,LAS_cookie,
+ subject, resource, auth_info,global_auth,
+ DS_LAS_ROLEDN, "DS_LASRoleDnAttrEval",
+ &lasinfo )) ) {
+ return LAS_EVAL_FAIL;
+ }
+
+ /* For anonymous client, they have no roles so the match is false. */
+ if ( lasinfo.anomUser )
+ return LAS_EVAL_FALSE;
+
+ /*
+ **
+ ** The function of this LAS is to find out if the client has
+ ** the role specified in the attr.
+ ** attr_pattern looks like: "ROLEDN cn=role1,o=sun.com"
+ */
+ attrName = attr_pattern;
+
+ matched = ACL_FALSE;
+ slapi_entry_attr_find( lasinfo.resourceEntry, attrName, &attr);
+ if (attr == NULL) {
+ /*
+ * Here the entry does not contain the attribute so the user
+ * cannot have this "null" role
+ */
+ return LAS_EVAL_FALSE;
+ }
+
+ if (lasinfo.aclpb->aclpb_optype == SLAPI_OPERATION_ADD) {
+ /*
+ * Here the entry does not contain the attribute so the user
+ * cannot have this "null" role or
+ * For the add operation, the resource itself
+ * must never be allowed to grant access--
+ * This is because access would be granted based on a value
+ * of an attribute in the new entry--security hole.
+ * XXX is this therefore FALSE or DONT_KNOW ?
+ *
+ *
+ */
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "ACL info: userattr=XXX#ROLEDN does not allow ADD permission.\n");
+ got_undefined = 1;
+ } else {
+
+ /*
+ * Got the first value.
+ * Test all the values of this attribute--if the client has _any_
+ * of the roles then it's a match.
+ */
+ k = slapi_attr_first_value ( attr,&sval );
+ while ( k != -1 ) {
+ char *n_attrval;
+ Slapi_DN *roleDN;
+
+ attrVal = slapi_value_get_berval ( sval );
+ n_attrval = slapi_ch_strdup( attrVal->bv_val);
+ n_attrval = slapi_dn_normalize (n_attrval);
+ roleDN = slapi_sdn_new_dn_byval(n_attrval);
+
+ /* We support: The attribute value can be a USER or a GROUP.
+ ** Let's compare with the client, thi might be just an user. If it is not
+ ** then we test it against the list of groups.
+ */
+ if ((matched = acllas__user_has_role(
+ lasinfo.aclpb,
+ roleDN,
+ lasinfo.aclpb->aclpb_authorization_sdn)) == ACL_TRUE){
+ slapi_ch_free ( (void **)&n_attrval );
+ slapi_sdn_free(&roleDN );
+ break;
+ }
+ slapi_ch_free ( (void **)&n_attrval );
+ slapi_sdn_free(&roleDN );
+ if (matched == ACL_TRUE) {
+ break;
+ } else if ( matched == ACL_DONT_KNOW ) {
+ /* record this but keep going--maybe another group will evaluate to TRUE */
+ got_undefined = 1;
+ }
+ k= slapi_attr_next_value ( attr, k, &sval );
+ }/* while */
+ }
+
+ /*
+ * If no terms were undefined, then evaluate as normal.
+ * If there was an undefined term, but another one was TRUE, then we also evaluate
+ * as normal. Otherwise, the whole expression is UNDEFINED.
+ */
+ if ( matched == ACL_TRUE || !got_undefined ) {
+ if (comparator == CMP_OP_EQ) {
+ rc = (matched == ACL_TRUE ? LAS_EVAL_TRUE : LAS_EVAL_FALSE);
+ } else {
+ rc = (matched == ACL_TRUE ? LAS_EVAL_FALSE : LAS_EVAL_TRUE);
+ }
+ } else {
+ rc = LAS_EVAL_FAIL;
+ }
+ return (rc);
+}
+
+/*
+ * Here, determine if lasinfo->clientDn matches user (which contains
+ * a ($dn) or a $attr component or both.) As defined in the aci
+ * lasinfo->aclpb->aclpb_curr_aci,
+ * which is the current aci being evaluated.
+ *
+ * returns: ACL_TRUE for matched,
+ * ACL_FALSE for matched.
+ * ACL_DONT_KNOW otherwise.
+ *
+ *
+*/
+
+int
+aclutil_evaluate_macro( char * rule, lasInfo *lasinfo,
+ acl_eval_types evalType ) {
+
+ int matched = 0;
+ aci_t *aci;
+ char *matched_val = NULL;
+ char **candidate_list = NULL;
+ char **inner_list = NULL;
+ char **sptr = NULL;
+ char **tptr = NULL;
+ char *t = NULL;
+ char *s = NULL;
+ char *target_dn = NULL;
+ struct acl_pblock *aclpb = lasinfo->aclpb;
+ int found_matched_val_in_ht = 0;
+
+ aci = lasinfo->aclpb->aclpb_curr_aci;
+ /* Get a pointer to the ndn in the resouirce */
+ target_dn = slapi_entry_get_ndn ( lasinfo->resourceEntry );
+
+ /*
+ * First, get the matched value from the target resource.
+ * We have alredy done this matching once beofer at tasrget match time.
+ */
+
+ LDAPDebug ( LDAP_DEBUG_ACL, "aclutil_evaluate_macro for aci '%s'"
+ "index '%d'\n",
+ aci->aclName, aci->aci_index,0);
+
+ if ( aci->aci_macro == NULL ) {
+ /* No $dn in the target, it's a $attr type subject rule */
+ matched_val = NULL;
+ } else {
+
+ /*
+ * Look up the matched_val value calculated
+ * from the target and stored judiciously there for us.
+ */
+
+ if ( (matched_val = (char *)acl_ht_lookup( aclpb->aclpb_macro_ht,
+ (PLHashNumber)aci->aci_index)) == NULL) {
+ LDAPDebug( LDAP_DEBUG_ACL,
+ "ACL info: failed to locate the calculated target"
+ "macro for aci '%s' index '%d'\n",
+ aci->aclName, aci->aci_index,0);
+ return(ACL_FALSE); /* Not a match */
+ } else {
+ LDAPDebug( LDAP_DEBUG_ACL,
+ "ACL info: found matched_val (%s) for aci index %d"
+ "in macro ht\n",
+ aci->aclName, aci->aci_index,0);
+
+ found_matched_val_in_ht = 1;
+ }
+ }
+
+ /*
+ * Now, make a candidate
+ * list of strings to match against the client.
+ * This involves replacing ($dn) or [$dn] by either the matched
+ * value, or all the suffix substrings of matched_val.
+ * If there is no $dn then the candidate list is just
+ * user itself.
+ *
+ */
+
+ candidate_list = acllas_replace_dn_macro( rule, matched_val, lasinfo);
+
+ sptr= candidate_list;
+ while( *sptr != NULL && !matched) {
+
+ s = *sptr;
+
+ /*
+ * Now s may contain some $attr macros.
+ * So, make a candidate list, got by replacing each occurence
+ * of $attr with all the values that attribute has in
+ * the resource entry.
+ */
+
+ inner_list = acllas_replace_attr_macro( s, lasinfo);
+
+ tptr = inner_list;
+ while( *tptr != NULL && (matched != ACL_TRUE) ){
+
+ t = *tptr;
+
+ /*
+ * Now, at last t is a candidate string we can
+ * match agains the client.
+ *
+ * $dn and $attr can appear in userdn, graoupdn and roledn
+ * rules, so we we need to decide which type we
+ * currently evaluating and evaluate that.
+ *
+ * If the string generated was undefined, eg it contained
+ * ($attr.ou) and the entry did not have an ou attribute,then
+ * the empty string is returned for this. So it we find
+ * an empty string in the list, skip it--it does not match.
+ */
+
+ if ( *t != '\0') {
+ if ( evalType == ACL_EVAL_USER ) {
+
+ matched = acllas_eval_one_user( lasinfo->aclpb,
+ lasinfo->clientDn, t);
+ } else if (evalType == ACL_EVAL_GROUP) {
+
+ matched = acllas_eval_one_group(t, lasinfo);
+ } else if (evalType == ACL_EVAL_ROLE) {
+ matched = acllas_eval_one_role(t, lasinfo);
+ } else if (evalType == ACL_EVAL_GROUPDNATTR) {
+ matched = acllas__eval_memberGroupDnAttr(t,
+ lasinfo->resourceEntry,
+ lasinfo->clientDn,
+ lasinfo->aclpb);
+ } else if ( evalType == ACL_EVAL_TARGET_FILTER) {
+
+ matched = acllas_eval_one_target_filter(t,
+ lasinfo->resourceEntry);
+
+ }
+ }
+
+ tptr++;
+
+ }/*inner while*/
+ charray_free(inner_list);
+
+ sptr++;
+ }/* outer while */
+
+ charray_free(candidate_list);
+
+ return(matched);
+
+}
+
+/*
+ * Here, replace the first occurrence of $(dn) with matched_val.
+ * replace any occurrence of $[dn] with each of the suffix substrings
+ * of matched_val.
+ * Each of these strings is returned in a NULL terminated list of strings.
+ *
+ * If there is no $dn thing then the returned list just contains rule itself.
+ *
+ * eg. rule: cn=fred,ou=*, ($dn), o=sun.com
+ * matched_val: ou=People,o=icnc
+ *
+ * Then we return the list
+ * cn=fred,ou=*,ou=People,o=icnc,o=sun.com NULL
+ *
+ * eg. rule: cn=fred,ou=*,[$dn], o=sun.com
+ * matched_val: ou=People,o=icnc
+ *
+ * Then we return the list
+ * cn=fred,ou=*,ou=People,o=icnc,o=sun.com
+ * cn=fred,ou=*,o=icnc,o=sun.com
+ * NULL
+ *
+ *
+*/
+
+static char **
+acllas_replace_dn_macro( char *rule, char *matched_val, lasInfo *lasinfo) {
+
+ char **a = NULL;
+ char *str = NULL;
+ char *patched_rule = NULL;
+ char *rule_to_use = NULL;
+ char *new_patched_rule = NULL;
+ char *rule_prefix = NULL;
+ char *rule_suffix = NULL;
+ int rule_suffix_len = 0;
+ char *comp = NULL;
+ int matched_val_len = 0;
+ int macro_len = 0;
+ int j = 0;
+ int has_macro_dn = 0;
+ int has_macro_levels = 0;
+
+ /* Determine what the rule's got once */
+ if ( strstr(rule, ACL_RULE_MACRO_DN_KEY) != NULL) {
+ has_macro_dn = 1;
+ }
+
+ if ( strstr(rule, ACL_RULE_MACRO_DN_LEVELS_KEY) != NULL) {
+ has_macro_levels = 1;
+ }
+
+ if ( !has_macro_dn && !has_macro_levels ) {
+
+ /*
+ * No $dn thing, just return a list with two elements, rule and NULL.
+ * charray_add will create the list and null terminate it.
+ */
+
+ charray_add( &a, slapi_ch_strdup(rule));
+ return(a);
+ } else {
+
+ /*
+ * Have an occurrence of the macro rules
+ *
+ * First, replace all occurrencers of ($dn) with the matched_val
+ */
+
+ if ( has_macro_dn) {
+ patched_rule =
+ acl_replace_str(rule, ACL_RULE_MACRO_DN_KEY, matched_val);
+ }
+
+ /* If there are no [$dn] we're done */
+
+ if ( !has_macro_levels ) {
+ charray_add( &a, patched_rule);
+ return(a);
+ } else {
+
+ /*
+ * It's a [$dn] type, so walk matched_val, splicing in all
+ * the suffix substrings and adding each such string to
+ * to the returned list.
+ * get_next_component() does not return the commas--the
+ * prefix and suffix should come with their commas.
+ *
+ * All occurrences of each [$dn] are replaced with each level.
+ *
+ * If has_macro_dn then patched_rule is the rule to strart with,
+ * and this needs to be freed at the end, otherwise
+ * just use rule.
+ */
+
+ if (patched_rule) {
+ rule_to_use = patched_rule;
+ } else {
+ rule_to_use = rule;
+ }
+
+ matched_val_len = strlen(matched_val);
+ j = 0;
+
+ while( j < matched_val_len) {
+
+ new_patched_rule =
+ acl_replace_str(rule_to_use, ACL_RULE_MACRO_DN_LEVELS_KEY,
+ &matched_val[j]);
+ charray_add( &a, new_patched_rule);
+
+ j += acl_find_comp_end(&matched_val[j]);
+ }
+
+ if (patched_rule) {
+ slapi_ch_free((void**)&patched_rule);
+ }
+
+ return(a);
+ }
+ }
+}
+
+/*
+ * Here, replace any occurrence of $attr.attrname with the
+ * value of attrname from lasinfo->resourceEntry.
+ *
+ *
+ * If there is no $attr thing then the returned list just contains rule
+ * itself.
+ *
+ * eg. rule: cn=fred,ou=*,ou=$attr.ou,o=sun.com
+ * ou: People
+ * ou: icnc
+ *
+ * Then we return the list
+ * cn=fred,ou=*,ou=People,o=sun.com
+ * cn=fred,ou=*,ou=icnc,o=sun.com
+ *
+*/
+
+static char **
+acllas_replace_attr_macro( char *rule, lasInfo *lasinfo) {
+
+ char **a = NULL;
+ char **working_list = NULL;
+ Slapi_Entry *e = lasinfo->resourceEntry;
+ char *str, *working_rule;
+ char *macro_str, *macro_attr_name;
+ int l;
+ Slapi_Attr *attr = NULL;
+
+ str = strstr(rule, ACL_RULE_MACRO_ATTR_KEY);
+ if ( str == NULL ) {
+
+ charray_add(&a, slapi_ch_strdup(rule));
+ return(a);
+
+ } else {
+
+ working_rule = slapi_ch_strdup(rule);
+ str = strstr(working_rule, ACL_RULE_MACRO_ATTR_KEY);
+ charray_add(&working_list, working_rule );
+
+ while( str != NULL) {
+
+ /*
+ * working_rule is the first member of working_list.
+ * str points to the next $attr.attrName in working_rule.
+ * each member of working_list needs to have each occurence of
+ * $attr.atrName replaced with the value of attrName in e.
+ * If attrName is multi valued then this generates another
+ * list which replaces the old one.
+ */
+
+ l = acl_strstr(&str[0], ")");
+ macro_str = slapi_ch_malloc(l+2);
+ strncpy( macro_str, &str[0], l+1);
+ macro_str[l+1] = '\0';
+
+ str = strstr(macro_str, ".");
+ str++; /* skip the . */
+ l = acl_strstr(&str[0], ")");
+ macro_attr_name = slapi_ch_malloc(l+1);
+ strncpy( macro_attr_name, &str[0], l);
+ macro_attr_name[l] = '\0';
+
+ slapi_entry_attr_find ( e, macro_attr_name, &attr );
+ if ( NULL == attr ) {
+
+ /*
+ * Here, if a $attr.attrName is such that the attrName
+ * does not occur in the entry then return a ""--
+ * this will go back to the matching code in
+ * aclutil_evaluate_macro() where "" will
+ * be taken as the candidate.
+ */
+
+ slapi_ch_free((void **)&macro_str);
+ slapi_ch_free((void **)&macro_attr_name);
+
+ charray_free(working_list);
+ charray_add(&a, slapi_ch_strdup(""));
+ return(a);
+
+ } else{
+
+ const struct berval *attrValue;
+ Slapi_Value *sval;
+ int i, j;
+ char *patched_rule;
+
+ a = NULL;
+ i= slapi_attr_first_value ( attr, &sval );
+ while(i != -1) {
+ attrValue = slapi_value_get_berval(sval);
+
+ j = 0;
+ while( working_list[j] != NULL) {
+
+ patched_rule =
+ acl_replace_str(working_list[j],
+ macro_str, attrValue->bv_val);
+ charray_add(&a, patched_rule);
+ j++;
+ }
+
+ i= slapi_attr_next_value( attr, i, &sval );
+ }/* while */
+
+ /*
+ * Here, a is working_list, where each member has had
+ * macro_str replaced with attrVal.
+ */
+
+ charray_free(working_list);
+ working_list = a;
+ working_rule = a[0];
+ }
+ slapi_ch_free((void **)&macro_str);
+ slapi_ch_free((void **)&macro_attr_name);
+
+ str = strstr(working_rule, ACL_RULE_MACRO_ATTR_KEY);
+
+ }/* while */
+
+ return(working_list);
+ }
+
+
+}
+
+/*
+ * returns ACL_TRUE, ACL_FALSE or ACL_DONT_KNOW.
+ *
+ * user is a string from the userdn keyword which may contain
+ * * components. This routine does the compare component by component, so
+ * that * behaves differently to "normal".
+ * Any ($dn) or $attr must have been removed from user before this is called.
+*/
+static int
+acllas_eval_one_user( struct acl_pblock *aclpb, char * clientDN, char *rule) {
+
+ int exact_match = 0;
+ int ret_code = 0;
+ const size_t LDAP_URL_prefix_len = strlen(LDAP_URL_prefix);
+ char *s = NULL;
+
+
+
+ /* URL format */
+ if ((s = strchr (rule, '?'))!= NULL) {
+ /* URL format */
+ if (acllas__client_match_URL ( aclpb, clientDN,
+ rule) == ACL_TRUE) {
+ exact_match = 1;
+ }
+ } else if ( strstr(rule, "=*") == NULL ) {
+ /* Just a straight compare */
+ /* skip the ldap:/// part */
+ rule += LDAP_URL_prefix_len;
+ exact_match = !slapi_utf8casecmp((ACLUCHP)clientDN,
+ (ACLUCHP)rule);
+ } else{
+ /* Here, contains a =*, so need to match comp by comp */
+ /* skip the ldap:/// part */
+ rule += LDAP_URL_prefix_len;
+ ret_code = acl_match_prefix( rule, clientDN, &exact_match);
+ }
+ if ( exact_match) {
+ return( ACL_TRUE);
+ } else {
+ return(ACL_FALSE);
+ }
+}
+
+/*
+ * returns ACL_TRUE, ACL_FALSE and ACL_DONT_KNOW.
+ *
+ * The user string has had all ($dn) and $attr replaced
+ * so the only dodgy thing left is a *.
+ *
+ * If * appears in such a user string, then it matches only that
+ * component, not .*, like it would otherwise.
+ *
+*/
+static int
+acllas_eval_one_group(char *groupbuf, lasInfo *lasinfo) {
+
+ if (groupbuf) {
+ return( acllas__user_ismember_of_group (
+ lasinfo->aclpb,
+ groupbuf,
+ lasinfo->clientDn,
+ ACLLAS_CACHE_ALL_GROUPS,
+ lasinfo->aclpb->aclpb_clientcert
+ ));
+ } else {
+ return(ACL_FALSE); /* not in the empty group */
+ }
+}
+
+/*
+ * returns ACL_TRUE for match, ACL_FALSE for not a match, ACL_DONT_KNOW otherwise.
+*/
+static int
+acllas_eval_one_role(char *role, lasInfo *lasinfo) {
+
+ Slapi_DN *roleDN = NULL;
+ int rc = ACL_FALSE;
+ char ebuf [ BUFSIZ ];
+
+ /*
+ * See if lasinfo.clientDn has role rolebuf.
+ * Here we know it's not an anom user nor
+ * a an anyone user--the client dn must be matched against
+ * a real role.
+ */
+
+ roleDN = slapi_sdn_new_dn_byval(role);
+ if (role) {
+ rc = acllas__user_has_role(
+ lasinfo->aclpb,
+ roleDN,
+ lasinfo->aclpb->aclpb_authorization_sdn);
+ } else { /* The user does not have the empty role */
+ rc = ACL_FALSE;
+ }
+ slapi_sdn_free(&roleDN );
+
+ /* Some useful logging */
+ if (rc == ACL_TRUE ) {
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "role evaluation: user '%s' does have role '%s'\n",
+ ACL_ESCAPE_STRING_WITH_PUNCTUATION (lasinfo->clientDn, ebuf),
+ role);
+ } else {
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "role evaluation: user '%s' does NOT have role '%s'\n",
+ ACL_ESCAPE_STRING_WITH_PUNCTUATION (lasinfo->clientDn, ebuf),
+ role);
+ }
+ return(rc);
+}
+
+/*
+ * returns ACL_TRUE if e matches the filter str, ACL_FALSE if not,
+ * ACL_DONT_KNOW otherwise.
+*/
+static int acllas_eval_one_target_filter( char * str, Slapi_Entry *e) {
+
+ int rc = ACL_FALSE;
+ Slapi_Filter *f = NULL;
+
+ if ((f = slapi_str2filter(str)) == NULL) {
+ slapi_log_error(SLAPI_LOG_FATAL, plugin_name,
+ "Warning: Bad targetfilter(%s) in aci: does not match\n", str);
+ return(ACL_DONT_KNOW);
+ }
+
+ if (slapi_vattr_filter_test(NULL, e, f, 0 /*don't do acess chk*/)!= 0) {
+ rc = ACL_FALSE; /* Filter does not match */
+ } else {
+ rc = ACL_TRUE; /* filter does match */
+ }
+ slapi_filter_free(f, 1);
+
+ return(rc);
+
+}
+
+
+
+
+
+/***************************************************************************/
+/* E N D */
+/***************************************************************************/
diff --git a/ldap/servers/plugins/acl/acllist.c b/ldap/servers/plugins/acl/acllist.c
new file mode 100644
index 00000000..0147626d
--- /dev/null
+++ b/ldap/servers/plugins/acl/acllist.c
@@ -0,0 +1,940 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+/************************************************************************
+ *
+ * ACLLIST
+ *
+ * All the ACLs are read when the server is started. The ACLs are
+ * parsed and kept in an AVL tree. All the ACL List management are
+ * in this file.
+ *
+ * The locking on the aci cache is implemented using the acllist_acicache*()
+ * routines--a read/write lock.
+ *
+ * The granularity of the view of the cache is the entry level--ie.
+ * when an entry is being modified (mod,add,delete,modrdn) and the mod
+ * involves the aci attribute, then other operations will see the acl cache
+ * before the whole change or after the whole change, but not during the change.
+ * cf. acl.c:acl_modified()
+ *
+ * The only tricky issue is that there is also locking
+ * implemented for the anonymous profile and sometimes we need to take both
+ * locks cf. aclanom_anom_genProfile(). The rule is
+ * always take the acicache lock first, followed by the anon lock--following
+ * this rule will ensure no dead lock scenarios can arise.
+ *
+ * Some routines are called in different places with different lock
+ * contexts--for these routines acl_lock_flag_t is used to
+ * pass the context.
+ *
+ */
+#include "acl.h"
+
+static PRRWLock *aci_rwlock = NULL;
+#define ACILIST_LOCK_READ() PR_RWLock_Rlock (aci_rwlock )
+#define ACILIST_UNLOCK_READ() PR_RWLock_Unlock (aci_rwlock )
+#define ACILIST_LOCK_WRITE() PR_RWLock_Wlock (aci_rwlock )
+#define ACILIST_UNLOCK_WRITE() PR_RWLock_Unlock (aci_rwlock )
+
+
+/* Root of the TREE */
+static Avlnode *acllistRoot = NULL;
+
+#define CONTAINER_INCR 2000
+
+/* The container array */
+static AciContainer **aciContainerArray;
+static PRUint32 currContainerIndex =0;
+static PRUint32 maxContainerIndex = 0;
+static int curAciIndex = 1;
+
+/* PROTOTYPES */
+static int __acllist_add_aci ( aci_t *aci );
+static int __acllist_aciContainer_node_cmp ( caddr_t d1, caddr_t d2 );
+static int __acllist_aciContainer_node_dup ( caddr_t d1, caddr_t d2 );
+static void __acllist_free_aciContainer ( AciContainer **container);
+static void free_targetattrfilters( Targetattrfilter ***input_attrFilterArray);
+
+void my_print( Avlnode *root );
+
+
+int
+acllist_init ()
+{
+
+ if (( aci_rwlock = PR_NewRWLock( PR_RWLOCK_RANK_NONE,"ACLLIST LOCK") ) == NULL ) {
+ slapi_log_error( SLAPI_LOG_FATAL, plugin_name,
+ "acllist_init:failed in getting the rwlock\n" );
+ return 1;
+ }
+
+ aciContainerArray = (AciContainer **) slapi_ch_calloc ( 1,
+ CONTAINER_INCR * sizeof ( AciContainer * ) );
+ maxContainerIndex = CONTAINER_INCR;
+ currContainerIndex = 0;
+
+ return 0;
+}
+
+/*
+ * This is the callback for backend state changes.
+ * It needs to add/remove acis as backends come up and go down.
+ *
+ * The strategy is simple:
+ * When a backend moves to the SLAPI_BE_STATE_ON then we go get all the acis
+ * add them to the cache.
+ * When a backend moves out of the SLAPI_BE_STATE_ON then we remove them all.
+ *
+*/
+
+void acl_be_state_change_fnc ( void *handle, char *be_name, int old_state,
+ int new_state) {
+ Slapi_Backend *be=NULL;
+ const Slapi_DN *sdn;
+
+
+ if ( old_state == SLAPI_BE_STATE_ON && new_state != SLAPI_BE_STATE_ON) {
+
+ slapi_log_error ( SLAPI_LOG_ACL, plugin_name,
+ "Backend %s is no longer STARTED--deactivating it's acis\n",
+ be_name);
+
+ if ( (be = slapi_be_select_by_instance_name( be_name )) == NULL) {
+ slapi_log_error ( SLAPI_LOG_ACL, plugin_name,
+ "Failed to retreive backend--NOT activating it's acis\n");
+ return;
+ }
+
+ /*
+ * Just get the first suffix--if there are multiple XXX ?
+ */
+
+ if ( (sdn = slapi_be_getsuffix( be, 0)) == NULL ) {
+ slapi_log_error ( SLAPI_LOG_ACL, plugin_name,
+ "Failed to retreive backend--NOT activating it's acis\n");
+ return;
+ }
+
+ aclinit_search_and_update_aci ( 1, /* thisbeonly */
+ sdn, /* base */
+ be_name,/* be name */
+ LDAP_SCOPE_SUBTREE,
+ ACL_REMOVE_ACIS,
+ DO_TAKE_ACLCACHE_WRITELOCK);
+
+ } else if ( old_state != SLAPI_BE_STATE_ON && new_state == SLAPI_BE_STATE_ON) {
+ slapi_log_error ( SLAPI_LOG_ACL, plugin_name,
+ "Backend %s is now STARTED--activating it's acis\n", be_name);
+
+ if ( (be = slapi_be_select_by_instance_name( be_name )) == NULL) {
+ slapi_log_error ( SLAPI_LOG_ACL, plugin_name,
+ "Failed to retreive backend--NOT activating it's acis\n");
+ return;
+ }
+
+ /*
+ * In fact there can onlt be one sufffix here.
+ */
+
+ if ( (sdn = slapi_be_getsuffix( be, 0)) == NULL ) {
+ slapi_log_error ( SLAPI_LOG_ACL, plugin_name,
+ "Failed to retreive backend--NOT activating it's acis\n");
+ return;
+ }
+
+ aclinit_search_and_update_aci ( 1, /* thisbeonly */
+ sdn,
+ be_name, /* be name */
+ LDAP_SCOPE_SUBTREE,
+ ACL_ADD_ACIS,
+ DO_TAKE_ACLCACHE_WRITELOCK);
+ }
+
+}
+
+/* This routine must be called with the acicache write lock taken */
+int
+acllist_insert_aci_needsLock( const Slapi_DN *e_sdn, const struct berval* aci_attr)
+{
+
+ aci_t *aci;
+ char *acl_str;
+ int rv =0;
+
+ if (aci_attr->bv_len <= 0)
+ return 0;
+
+ aci = acllist_get_aci_new ();
+ slapi_sdn_set_ndn_byval ( aci->aci_sdn, slapi_sdn_get_ndn ( e_sdn ) );
+
+ acl_str = slapi_ch_strdup(aci_attr->bv_val);
+ /* Parse the ACL TEXT */
+ if ( 0 != (rv = acl_parse ( acl_str, aci )) ) {
+ slapi_log_error (SLAPI_LOG_FATAL, plugin_name,
+ "ACL PARSE ERR(rv=%d): %s\n", rv, acl_str );
+ slapi_ch_free ( (void **) &acl_str );
+ acllist_free_aci ( aci );
+
+ return 1;
+ }
+
+ /* Now add it to the list */
+ if ( 0 != (rv =__acllist_add_aci ( aci ))) {
+ slapi_log_error (SLAPI_LOG_ACL, plugin_name,
+ "ACL ADD ACI ERR(rv=%d): %s\n", rv, acl_str );
+ slapi_ch_free ( (void **) &acl_str );
+ acllist_free_aci ( aci );
+ return 1;
+ }
+
+ slapi_ch_free ( (void **) &acl_str );
+ acl_regen_aclsignature ();
+ if ( aci->aci_elevel == ACI_ELEVEL_USERDN_ANYONE)
+ aclanom_invalidateProfile ();
+ return 0;
+}
+
+/* This routine must be called with the acicache write lock taken */
+static int
+__acllist_add_aci ( aci_t *aci )
+{
+
+ int rv = 0; /* OK */
+ AciContainer *aciListHead;
+ AciContainer *head;
+ PRUint32 i;
+
+ aciListHead = acllist_get_aciContainer_new ( );
+ slapi_sdn_set_ndn_byval ( aciListHead->acic_sdn, slapi_sdn_get_ndn ( aci->aci_sdn ) );
+
+ /* insert the aci */
+ switch (avl_insert ( &acllistRoot, aciListHead, __acllist_aciContainer_node_cmp,
+ __acllist_aciContainer_node_dup ) ) {
+
+ case 1: /* duplicate ACL on the same entry */
+
+ /* Find the node that contains the acl. */
+ if ( NULL == (head = (AciContainer *) avl_find( acllistRoot, aciListHead,
+ (IFP) __acllist_aciContainer_node_cmp ) ) ) {
+ slapi_log_error ( SLAPI_PLUGIN_ACL, plugin_name,
+ "Can't insert the acl in the tree\n");
+ rv = 1;
+ } else {
+ aci_t *t_aci;;
+
+ /* Attach the list */
+ t_aci = head->acic_list;;
+ while ( t_aci && t_aci->aci_next )
+ t_aci = t_aci->aci_next;
+
+ /* Now add the new one to the end of the list */
+ t_aci->aci_next = aci;
+ }
+ slapi_log_error ( SLAPI_LOG_ACL, plugin_name, "Added the ACL:%s to existing container:[%d]%s\n",
+ aci->aclName, head->acic_index, slapi_sdn_get_ndn( head->acic_sdn ));
+
+ /* now free the tmp container */
+ aciListHead->acic_list = NULL;
+ __acllist_free_aciContainer ( &aciListHead );
+
+ break;
+ default:
+ /* The container is inserted. Now hook up the aci and setup the
+ * container index. Donot free the "aciListHead" here.
+ */
+ aciListHead->acic_list = aci;
+
+ /*
+ * First, see if we have an open slot or not - -if we have reuse it
+ */
+ i = 0;
+ while ( (i < currContainerIndex) && aciContainerArray[i] )
+ i++;
+
+ if ( currContainerIndex >= (maxContainerIndex - 2)) {
+ maxContainerIndex += CONTAINER_INCR;
+ aciContainerArray = (AciContainer **) slapi_ch_realloc ( (char *) aciContainerArray,
+ maxContainerIndex * sizeof ( AciContainer * ) );
+ }
+ aciListHead->acic_index = i;
+ /* If i < currContainerIndex, we are just re-using an old slot. */
+ /* We don't need to increase currContainerIndex if we just re-use an old one. */
+ if (i == currContainerIndex)
+ currContainerIndex++;
+
+ aciContainerArray[ aciListHead->acic_index ] = aciListHead;
+
+ slapi_log_error ( SLAPI_LOG_ACL, plugin_name, "Added %s to container:%d\n",
+ slapi_sdn_get_ndn( aciListHead->acic_sdn ), aciListHead->acic_index );
+ break;
+ }
+
+ return rv;
+}
+
+
+
+static int
+__acllist_aciContainer_node_cmp ( caddr_t d1, caddr_t d2 )
+{
+
+ int rc =0;
+ AciContainer *c1 = (AciContainer *) d1;
+ AciContainer *c2 = (AciContainer *) d2;
+
+
+ rc = slapi_sdn_compare ( c1->acic_sdn, c2->acic_sdn );
+ return rc;
+}
+
+static int
+__acllist_aciContainer_node_dup ( caddr_t d1, caddr_t d2 )
+{
+
+ /* we allow duplicates -- they are not exactly duplicates
+ ** but multiple aci value on the same node
+ */
+ return 1;
+
+}
+
+
+/*
+ * Remove the ACL
+ *
+ * This routine must be called with the aclcache write lock taken.
+ * It takes in addition the one for the anom profile taken in
+ * aclanom_invalidateProfile().
+ * They _must_ be taken in this order or there
+ * is a deadlock scenario with aclanom_gen_anomProfile() which
+ * also takes them is this order.
+*/
+
+int
+acllist_remove_aci_needsLock( const Slapi_DN *sdn, const struct berval *attr )
+{
+
+ aci_t *head, *next;
+ int rv = 0;
+ AciContainer *aciListHead, *root;
+ AciContainer *dContainer;
+ int removed_anom_acl = 0;
+
+ /* we used to delete the ACL by value but we don't do that anymore.
+ * rather we delete all the acls in that entry and then repopulate it if
+ * there are any more acls.
+ */
+
+ aciListHead = acllist_get_aciContainer_new ( );
+ slapi_sdn_set_ndn_byval ( aciListHead->acic_sdn, slapi_sdn_get_ndn ( sdn ) );
+
+ /* now find it */
+ if ( NULL == (root = (AciContainer *) avl_find( acllistRoot, aciListHead,
+ (IFP) __acllist_aciContainer_node_cmp ))) {
+ /* In that case we don't have any acl for this entry. cool !!! */
+
+ __acllist_free_aciContainer ( &aciListHead );
+ slapi_log_error ( SLAPI_LOG_ACL, plugin_name,
+ "No acis to remove in this entry\n" );
+ return 0;
+ }
+
+ head = root->acic_list;
+ if ( head)
+ next = head->aci_next;
+ while ( head ) {
+ if ( head->aci_elevel == ACI_ELEVEL_USERDN_ANYONE)
+ removed_anom_acl = 1;
+
+ /* Free the acl */
+ acllist_free_aci ( head );
+
+ head = next;
+ next = NULL;
+ if ( head && head->aci_next )
+ next = head->aci_next;
+ }
+ root->acic_list = NULL;
+
+ /* remove the container from the slot */
+ aciContainerArray[root->acic_index] = NULL;
+
+ slapi_log_error ( SLAPI_LOG_ACL, plugin_name,
+ "Removing container[%d]=%s\n", root->acic_index,
+ slapi_sdn_get_ndn ( root->acic_sdn) );
+ dContainer = (AciContainer *) avl_delete ( &acllistRoot, aciListHead,
+ __acllist_aciContainer_node_cmp );
+ __acllist_free_aciContainer ( &dContainer );
+
+ acl_regen_aclsignature ();
+ if ( removed_anom_acl )
+ aclanom_invalidateProfile ();
+
+ /*
+ * Now read back the entry and repopulate ACLs for that entry, but
+ * only if a specific aci was deleted, otherwise, we do a
+ * "When Harry met Sally" and nail 'em all.
+ */
+
+ if ( attr != NULL) {
+
+ if (0 != (rv = aclinit_search_and_update_aci ( 0, /* thisbeonly */
+ sdn, /* base */
+ NULL, /* be name */
+ LDAP_SCOPE_BASE,
+ ACL_ADD_ACIS,
+ DONT_TAKE_ACLCACHE_WRITELOCK))) {
+ slapi_log_error ( SLAPI_LOG_FATAL, plugin_name,
+ " Can't add the rest of the acls for entry:%s after delete\n",
+ slapi_sdn_get_dn ( sdn ) );
+ }
+ }
+
+ /* Now free the tmp container we used */
+ __acllist_free_aciContainer ( &aciListHead );
+
+ /*
+ * regenerate the anonymous profile if we have deleted
+ * anyone acls.
+ * We don't need the aclcache readlock because the context of
+ * this routine is we have the write lock already.
+ */
+ if ( removed_anom_acl )
+ aclanom_gen_anomProfile(DONT_TAKE_ACLCACHE_READLOCK);
+
+ return rv;
+}
+
+AciContainer *
+acllist_get_aciContainer_new ( )
+{
+
+ AciContainer *head;
+
+ head = (AciContainer * ) slapi_ch_calloc ( 1, sizeof ( AciContainer ) );
+ head->acic_sdn = slapi_sdn_new ( );
+ head->acic_index = -1;
+
+ return head;
+}
+static void
+__acllist_free_aciContainer ( AciContainer **container)
+{
+
+ PR_ASSERT ( container != NULL );
+
+ if ( (*container)->acic_index >= 0 )
+ aciContainerArray[ (*container)->acic_index] = NULL;
+ if ( (*container)->acic_sdn )
+ slapi_sdn_free ( &(*container)->acic_sdn );
+ slapi_ch_free ( (void **) container );
+
+}
+
+void
+acllist_done_aciContainer ( AciContainer *head )
+{
+
+ PR_ASSERT ( head != NULL );
+
+ slapi_sdn_done ( head->acic_sdn );
+ head->acic_index = -1;
+
+ /* The caller is responsible for taking care of list */
+ head->acic_list = NULL;
+}
+
+
+aci_t *
+acllist_get_aci_new ()
+{
+ aci_t *aci_item;
+
+ aci_item = (aci_t *) slapi_ch_calloc (1, sizeof (aci_t));
+ aci_item->aci_sdn = slapi_sdn_new ();
+ aci_item->aci_index = curAciIndex++;
+ aci_item->aci_elevel = ACI_DEFAULT_ELEVEL; /* by default it's a complex */
+ aci_item->targetAttr = (Targetattr **) slapi_ch_calloc (
+ ACL_INIT_ATTR_ARRAY,
+ sizeof (Targetattr *));
+ return aci_item;
+}
+
+void
+acllist_free_aci(aci_t *item)
+{
+
+ Targetattr **attrArray;
+
+ /* The caller is responsible for taking
+ ** care of list issue
+ */
+ if (item == NULL) return;
+
+ slapi_sdn_free ( &item->aci_sdn );
+ slapi_filter_free (item->target, 1);
+
+ /* slapi_filter_free(item->targetAttr, 1); */
+ attrArray = item->targetAttr;
+ if (attrArray) {
+ int i = 0;
+ Targetattr *attr;
+
+ while (attrArray[i] != NULL) {
+ attr = attrArray[i];
+ if (attr->attr_type & ACL_ATTR_FILTER) {
+ slapi_filter_free(attr->u.attr_filter, 1);
+ } else {
+ slapi_ch_free ( (void **) &attr->u.attr_str );
+ }
+ slapi_ch_free ( (void **) &attr );
+ i++;
+ }
+ /* Now free the array */
+ slapi_ch_free ( (void **) &attrArray );
+ }
+
+ /* Now free any targetattrfilters in this aci item */
+
+ if ( item->targetAttrAddFilters ) {
+ free_targetattrfilters(&item->targetAttrAddFilters);
+ }
+
+ if ( item->targetAttrDelFilters ) {
+ free_targetattrfilters(&item->targetAttrDelFilters);
+ }
+
+ if (item->targetFilterStr) slapi_ch_free ( (void **) &item->targetFilterStr );
+ slapi_filter_free(item->targetFilter, 1);
+
+ /* free the handle */
+ if (item->aci_handle) ACL_ListDestroy(NULL, item->aci_handle);
+
+ /* Free the name */
+ if (item->aclName) slapi_ch_free((void **) &item->aclName);
+
+ /* Free any macro info*/
+ if (item->aci_macro) {
+ slapi_ch_free((void **) &item->aci_macro->match_this);
+ slapi_ch_free((void **) &item->aci_macro);
+ }
+
+ /* free at last -- free at last */
+ slapi_ch_free ( (void **) &item );
+}
+
+static void free_targetattrfilters( Targetattrfilter ***attrFilterArray) {
+
+ if (*attrFilterArray) {
+ int i = 0;
+ Targetattrfilter *attrfilter;
+
+ while ((*attrFilterArray)[i] != NULL) {
+ attrfilter = (*attrFilterArray)[i];
+
+ if ( attrfilter->attr_str != NULL) {
+ slapi_ch_free ( (void **) &attrfilter->attr_str );
+ }
+
+ if (attrfilter->filter != NULL) {
+ slapi_filter_free(attrfilter->filter, 1);
+ }
+
+ if( attrfilter->filterStr != NULL) {
+ slapi_ch_free ( (void **) &attrfilter->filterStr );
+ }
+
+ slapi_ch_free ( (void **) &attrfilter );
+ i++;
+ }
+ /* Now free the array */
+ slapi_ch_free ( (void **) attrFilterArray );
+ }
+
+}
+
+/* SEARCH */
+void
+acllist_init_scan (Slapi_PBlock *pb, int scope, char *base)
+{
+ Acl_PBlock *aclpb;
+ int i;
+ AciContainer *root;
+ char *basedn = NULL;
+ int index;
+
+ if ( acl_skip_access_check ( pb, NULL ) ) {
+ return;
+ }
+
+ /*acllist_print_tree ( acllistRoot, &depth, "top", "top") ; */
+ /* my_print ( acllistRoot );*/
+ /* If we have an anonymous profile and I am an anom dude - let's skip it */
+ if ( aclanom_is_client_anonymous ( pb )) {
+ return;
+ }
+ aclpb = acl_get_aclpb (pb, ACLPB_BINDDN_PBLOCK );
+ if ( !aclpb ) {
+ slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, "Missing aclpb 4 \n" );
+ return;
+ }
+
+ aclpb->aclpb_handles_index[0] = -1;
+
+ /* If base is NULL - it means we are going to go thru all the ACLs
+ * This is needed when we do anonymous profile generation.
+ */
+ if ( NULL == base ) {
+ return;
+ }
+
+ aclpb->aclpb_state |= ACLPB_SEARCH_BASED_ON_LIST ;
+
+ acllist_acicache_READ_LOCK();
+
+ basedn = slapi_ch_strdup (base);
+ index = 0;
+ aclpb->aclpb_search_base = slapi_ch_strdup ( base );
+
+ while (basedn ) {
+ char *tmp = NULL;
+
+ slapi_sdn_set_ndn_byref ( aclpb->aclpb_aclContainer->acic_sdn, basedn );
+
+ root = (AciContainer *) avl_find( acllistRoot,
+ (caddr_t) aclpb->aclpb_aclContainer,
+ (IFP) __acllist_aciContainer_node_cmp);
+ if ( index >= ACLPB_MAX_SELECTED_ACLS -2 ) {
+ aclpb->aclpb_handles_index[0] = -1;
+ slapi_ch_free ( (void **) &basedn);
+ break;
+ } else if ( NULL != root ) {
+ aclpb->aclpb_base_handles_index[index++] = root->acic_index;
+ aclpb->aclpb_base_handles_index[index] = -1;
+ }
+ tmp = slapi_dn_parent ( basedn );
+ slapi_ch_free ( (void **) &basedn);
+ basedn = tmp;
+ }
+
+ acllist_done_aciContainer ( aclpb->aclpb_aclContainer);
+
+ if ( aclpb->aclpb_base_handles_index[0] == -1 )
+ aclpb->aclpb_state &= ~ACLPB_SEARCH_BASED_ON_LIST ;
+
+ acllist_acicache_READ_UNLOCK();
+
+ i = 0;
+ while ( i < ACLPB_MAX_SELECTED_ACLS && aclpb->aclpb_base_handles_index[i] != -1 ) {
+ i++;
+ }
+}
+
+/*
+ * Initialize aclpb_handles_index[] (sentinel -1) to
+ * contain a list of all aci items at and above edn in the DIT tree.
+ * This list will be subsequestly scanned to find applicable aci's for
+ * the given operation.
+*/
+
+void
+acllist_aciscan_update_scan ( Acl_PBlock *aclpb, char *edn )
+{
+
+ int i, index = 0;
+ char *basedn = NULL;
+ AciContainer *root;
+ int is_not_search_base = 1;
+
+
+ /* First copy the containers indx from the base to the one which is
+ * going to be used.
+ * The base handles get done in acllist_init_scan().
+ * This stuff is only used if it's a search operation.
+ */
+ if ( aclpb && aclpb->aclpb_search_base ) {
+ while ( aclpb->aclpb_base_handles_index[index] != -1 &&
+ index < ACLPB_MAX_SELECTED_ACLS -2 ) {
+ aclpb->aclpb_handles_index[index] =
+ aclpb->aclpb_base_handles_index[index];
+ index++;
+ }
+ if ( strcasecmp ( edn, aclpb->aclpb_search_base) == 0) {
+ is_not_search_base = 0;
+ }
+ }
+ aclpb->aclpb_handles_index[index] = -1;
+
+ /*
+ * Here, make a list of all the aci's that will apply
+ * to edn ie. all aci's at and above edn in the DIT tree.
+ *
+ * Do this by walking up edn, looking at corresponding
+ * points in the acllistRoot aci tree.
+ *
+ * If is_not_search_base is true, then we need to iterate on edn, otherwise
+ * we've already got all the base handles above.
+ *
+ */
+
+ if (is_not_search_base) {
+
+ basedn = slapi_ch_strdup ( edn );
+
+ while (basedn ) {
+ char *tmp = NULL;
+
+ slapi_sdn_set_ndn_byref ( aclpb->aclpb_aclContainer->acic_sdn, basedn );
+
+ root = (AciContainer *) avl_find( acllistRoot,
+ (caddr_t) aclpb->aclpb_aclContainer,
+ (IFP) __acllist_aciContainer_node_cmp);
+
+ slapi_log_error ( SLAPI_LOG_ACL, plugin_name,
+ "Searching AVL tree for update:%s: container:%d\n", basedn ,
+ root ? root->acic_index: -1);
+ if ( index >= ACLPB_MAX_SELECTED_ACLS -2 ) {
+ aclpb->aclpb_handles_index[0] = -1;
+ slapi_ch_free ( (void **) &basedn);
+ break;
+ } else if ( NULL != root ) {
+ aclpb->aclpb_handles_index[index++] = root->acic_index;
+ aclpb->aclpb_handles_index[index] = -1;
+ }
+ tmp = slapi_dn_parent ( basedn );
+ slapi_ch_free ( (void **) &basedn);
+ basedn = tmp;
+ if ( aclpb->aclpb_search_base && tmp &&
+ ( 0 == strcasecmp ( tmp, aclpb->aclpb_search_base))) {
+ slapi_ch_free ( (void **) &basedn);
+ tmp = NULL;
+ }
+ } /* while */
+ }
+
+ acllist_done_aciContainer ( aclpb->aclpb_aclContainer );
+ i = 0;
+ while ( i < ACLPB_MAX_SELECTED_ACLS && aclpb->aclpb_handles_index[i] != -1 ) {
+ i++;
+ }
+
+}
+
+aci_t *
+acllist_get_first_aci (Acl_PBlock *aclpb, PRUint32 *cookie )
+{
+
+ int val;
+
+ *cookie = val = 0;
+ if ( aclpb && aclpb->aclpb_handles_index[0] != -1 ) {
+ val = aclpb->aclpb_handles_index[*cookie];
+ }
+ if ( NULL == aciContainerArray[val]) {
+ return ( acllist_get_next_aci ( aclpb, NULL, cookie ) );
+ }
+
+ return (aciContainerArray[val]->acic_list );
+}
+/*
+ * acllist_get_next_aci
+ * Return the next aci in the list
+ *
+ * Inputs
+ * Acl_PBlock *aclpb -- acl Main block; if aclpb= NULL,
+ * -- then we scan thru the whole list.
+ * -- which is used by anom profile code.
+ * aci_t *curaci -- the current aci
+ * PRUint32 *cookie -- cookie -- to maintain a state (what's next)
+ *
+ */
+
+aci_t *
+acllist_get_next_aci ( Acl_PBlock *aclpb, aci_t *curaci, PRUint32 *cookie )
+{
+ PRUint32 val;
+ int scan_entire_list;
+
+ /*
+ Here, if we're passed a curaci and there's another aci in the same node,
+ return that one.
+ */
+
+ if ( curaci && curaci->aci_next )
+ return ( curaci->aci_next );
+
+ /*
+ Determine if we need to scan the entire list of acis.
+ We do if the aclpb==NULL or if the first handle index is -1.
+ That means that we want to go through
+ the entire aciContainerArray up to the currContainerIndex to get
+ acis; the -1 in the first position is a special keyword which tells
+ us that the acis have changed, so we need to go through all of them.
+ */
+
+ scan_entire_list = (aclpb == NULL || aclpb->aclpb_handles_index[0] == -1);
+
+start:
+ (*cookie)++;
+ val = *cookie;
+
+ /* if we are not scanning the entire aciContainerArray list, we only want to
+ look at the indexes specified in the handles index */
+ if ( !scan_entire_list )
+ val = aclpb->aclpb_handles_index[*cookie];
+
+ /* the hard max end */
+ if ( val >= maxContainerIndex)
+ return NULL;
+
+ /* reached the end of the array */
+ if ((!scan_entire_list && (*cookie >= (ACLPB_MAX_SELECTED_ACLS-1))) ||
+ (*cookie >= currContainerIndex)) {
+ return NULL;
+ }
+
+ /* if we're only using the handles list for our aciContainerArray
+ indexes, the -1 value marks the end of that list */
+ if ( !scan_entire_list && (aclpb->aclpb_handles_index[*cookie] == -1) ) {
+ return NULL;
+ }
+
+ /* if we're scanning the entire list, and we hit a null value in the
+ middle of the list, just try the next one; this can happen if
+ an aci was deleted - it can leave "holes" in the array */
+ if ( scan_entire_list && ( NULL == aciContainerArray[val])) {
+ goto start;
+ }
+
+ if ( aciContainerArray[val] )
+ return (aciContainerArray[val]->acic_list );
+ else
+ return NULL;
+}
+
+void
+acllist_acicache_READ_UNLOCK( )
+{
+ ACILIST_UNLOCK_READ ();
+
+}
+
+void
+acllist_acicache_READ_LOCK()
+{
+ /* get a reader lock */
+ ACILIST_LOCK_READ ();
+
+}
+
+void
+acllist_acicache_WRITE_UNLOCK( )
+{
+ ACILIST_UNLOCK_WRITE ();
+
+}
+
+void
+acllist_acicache_WRITE_LOCK( )
+{
+ ACILIST_LOCK_WRITE ();
+
+}
+
+/* This routine must be called with the acicache write lock taken */
+int
+acllist_moddn_aci_needsLock ( Slapi_DN *oldsdn, char *newdn )
+{
+
+
+ AciContainer *aciListHead;
+ AciContainer *head;
+
+ /* first get the container */
+
+ aciListHead = acllist_get_aciContainer_new ( );
+ slapi_sdn_free(&aciListHead->acic_sdn);
+ aciListHead->acic_sdn = oldsdn;
+
+
+ if ( NULL == (head = (AciContainer *) avl_find( acllistRoot, aciListHead,
+ (IFP) __acllist_aciContainer_node_cmp ) ) ) {
+
+ slapi_log_error ( SLAPI_PLUGIN_ACL, plugin_name,
+ "Can't find the acl in the tree for moddn operation:olddn%s\n",
+ slapi_sdn_get_ndn ( oldsdn ));
+ aciListHead->acic_sdn = NULL;
+ __acllist_free_aciContainer ( &aciListHead );
+ return 1;
+ }
+
+
+ /* Now set the new DN */
+ slapi_sdn_done ( head->acic_sdn );
+ slapi_sdn_set_ndn_byval ( head->acic_sdn, newdn );
+
+ aciListHead->acic_sdn = NULL;
+ __acllist_free_aciContainer ( &aciListHead );
+
+ return 0;
+}
+
+void
+acllist_print_tree ( Avlnode *root, int *depth, char *start, char *side)
+{
+
+ AciContainer *aciHeadList;
+
+ if ( NULL == root ) {
+ return;
+ }
+ aciHeadList = (AciContainer *) root->avl_data;
+ slapi_log_error ( SLAPI_LOG_ACL, "plugin_name",
+ "Container[ Depth=%d%s-%s]: %s\n", *depth, start, side,
+ slapi_sdn_get_ndn ( aciHeadList->acic_sdn ) );
+
+ (*depth)++;
+
+ acllist_print_tree ( root->avl_left, depth, side, "L" );
+ acllist_print_tree ( root->avl_right, depth, side, "R" );
+
+ (*depth)--;
+
+}
+
+static
+void
+ravl_print( Avlnode *root, int depth )
+{
+ int i;
+
+ AciContainer *aciHeadList;
+ if ( root == 0 )
+ return;
+
+ ravl_print( root->avl_right, depth+1 );
+
+ for ( i = 0; i < depth; i++ )
+ printf( " " );
+ aciHeadList = (AciContainer *) root->avl_data;
+ printf( "%s\n", slapi_sdn_get_ndn ( aciHeadList->acic_sdn ) );
+
+ ravl_print( root->avl_left, depth+1 );
+}
+
+void
+my_print( Avlnode *root )
+{
+ printf( "********\n" );
+
+ if ( root == 0 )
+ printf( "\tNULL\n" );
+ else
+ ( void ) ravl_print( root, 0 );
+
+ printf( "********\n" );
+}
diff --git a/ldap/servers/plugins/acl/aclparse.c b/ldap/servers/plugins/acl/aclparse.c
new file mode 100644
index 00000000..2247471a
--- /dev/null
+++ b/ldap/servers/plugins/acl/aclparse.c
@@ -0,0 +1,1928 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include "acl.h"
+
+/****************************************************************************/
+/* prototypes */
+/****************************************************************************/
+static int __aclp__parse_aci(char *str, aci_t *aci_item);
+static int __aclp__sanity_check_acltxt(aci_t *aci_item, char *str);
+static char * __aclp__normalize_acltxt (aci_t *aci_item, char *str);
+static char * __aclp__getNextLASRule(aci_t *aci_item, char *str,
+ char **endOfCurrRule);
+static char * __aclp__dn_normalize( char *dn , char *end);
+static int __aclp__get_aci_right ( char *str);
+static int __aclp__init_targetattr (aci_t *aci, char *attr_val);
+static int __acl__init_targetattrfilters( aci_t *aci_item, char *str);
+static int process_filter_list( Targetattrfilter ***attrfilterarray,
+ char * str);
+static int __acl_init_targetattrfilter( Targetattrfilter *attrfilter, char *str );
+static void __aclp_chk_paramRules ( aci_t *aci_item, char *start,
+ char *end);
+static void __acl_strip_trailing_space( char *str);
+static void __acl_strip_leading_space( char **str);
+static char * __acl_trim_filterstr( char * str );
+static int acl_verify_exactly_one_attribute( char *attr_name, Slapi_Filter *f);
+static int type_compare( Slapi_Filter *f, void *arg);
+static int acl_check_for_target_macro( aci_t *aci_item, char *value);
+static int get_acl_rights_as_int( char * strValue);
+
+/***************************************************************************
+*
+* acl_parse
+*
+* Parses the input string and copies the information into the
+* correct place in the aci.
+*
+*
+* Input:
+* char *str - Input string which has the ACL
+* This is a duped copy, so here we have
+* the right to stich '\0' characters into str for
+* processing purposes. If you want to keep
+* a piece of str, you'll need to dup it
+* as it gets freed outside the scope of acl_parse.
+* aci_t *item - the aci item where the ACL info will be
+* - stored.
+*
+* Returns:
+* 0 -- Parsed okay
+* < 0 -- error codes
+*
+* Error Handling:
+* None.
+*
+**************************************************************************/
+int
+acl_parse(char * str, aci_t *aci_item)
+{
+
+ int rv=0;
+ char *next;
+ char *save;
+
+ while(*str) {
+ __acl_strip_leading_space( &str );
+ if (*str == '\0') break;
+
+ if (*str == '(') {
+ if ((next = slapi_find_matching_paren(str)) == NULL) {
+ return(ACL_SYNTAX_ERR);
+ }
+ } else {
+ /* then we have done all the processing */
+ return 0;
+ }
+ LDAP_UTF8INC(str); /* skip the "(" */
+ save = next;
+ LDAP_UTF8INC(next);
+ *save = '\0';
+
+ /* Now we have a "str)" */
+ if ( 0 != (rv = __aclp__parse_aci(str, aci_item))) {
+ return(rv);
+ }
+
+ /* Move to the next */
+ str = next;
+ }
+
+ /* check if have a ACLTXT or not */
+ if (!(aci_item->aci_type & ACI_ACLTXT))
+ return ACL_SYNTAX_ERR;
+
+ if (aci_item->target) {
+ Slapi_Filter *f;
+
+ /* Make sure that the target is a valid target.
+ ** Example: ACL is located in
+ ** "ou=engineering, o=ace industry, c=us
+ ** but if the target is "o=ace industry, c=us",
+ ** then it's an ERROR.
+ */
+ f = aci_item->target;
+ if (aci_item->aci_type & ACI_TARGET_DN) {
+ char *avaType;
+ struct berval *avaValue;
+ const char *dn;
+
+ dn = slapi_sdn_get_ndn ( aci_item->aci_sdn );
+ slapi_filter_get_ava ( f, &avaType, &avaValue );
+
+ if (!slapi_dn_issuffix( avaValue->bv_val, dn))
+ return ACL_INVALID_TARGET;
+ }
+ }
+
+ /*
+ ** We need to keep the taregetFilterStr for anyone ACL only.
+ ** same for targetValueFilterStr.
+ ** We need to keep it for macros too as it needs to be expnaded at eval time.
+ **
+ */
+ if ( (aci_item->aci_elevel != ACI_ELEVEL_USERDN_ANYONE) &&
+ !(aci_item->aci_type & ACI_TARGET_MACRO_DN) ) {
+ slapi_ch_free ( (void **) & aci_item->targetFilterStr );
+ }
+
+ /*
+ * If we parsed the aci and there was a ($dn) on the user side
+ * but none in hte taget then that's an error as the user side
+ * value is derived from the target side value.
+ */
+
+ if (!(aci_item->aci_type & ACI_TARGET_MACRO_DN) &&
+ (aci_item->aci_ruleType & ACI_PARAM_DNRULE)) {
+ slapi_log_error(SLAPI_LOG_FATAL, plugin_name,
+ "acl_parse: A macro in a subject ($dn) must have a macro in the target.\n");
+ return(ACL_INVALID_TARGET);
+ }
+
+ return 0;
+}
+
+/***************************************************************************
+*
+* __aclp__parse_aci
+*
+* Parses Each individual subset of information/
+*
+* Input:
+* char *str - Input string which has the ACL like "str)"
+* aci_t *item - the aci item where the ACL info will be
+* - stored.
+*
+* Returns:
+* 0 -- Parsed okay
+* < 0 -- error codes
+*
+* Error Handling:
+* None.
+*
+**************************************************************************/
+static int
+__aclp__parse_aci (char *str, aci_t *aci_item)
+{
+
+ int len;
+ int rv;
+ int type;
+ char *tmpstr;
+ char *s = NULL;
+ char *value = NULL;
+ Slapi_Filter *f = NULL;
+ int targetattrlen = strlen(aci_targetattr);
+ int targetdnlen = strlen (aci_targetdn);
+ int tfilterlen = strlen(aci_targetfilter);
+ int targetattrfilterslen = strlen(aci_targetattrfilters);
+
+ __acl_strip_leading_space( &str );
+
+ if (*str == '\0') {
+ return(ACL_SYNTAX_ERR);
+ }
+
+ /* The first letter should tell us something */
+ switch(*str) {
+ case 'v':
+ type = ACI_ACLTXT;
+
+ if ( 0 != (rv= __aclp__sanity_check_acltxt(aci_item, str ) ) ) {
+
+ return rv;
+ }
+ break;
+
+ case 't':
+ if (strncmp(str, aci_targetattrfilters,targetattrfilterslen ) == 0) {
+ type = ACI_TARGET_ATTR;
+
+
+ /*
+ * The targetattrfilters bit looks like this:
+ * (targetattrfilters="add= attr1:F1 && attr2:F2 ... && attrn:Fn,
+ * del= attr1:F1 && attr2:F2... && attrn:Fn")
+ */
+ if ( 0 != (rv= __acl__init_targetattrfilters(
+ aci_item, str))) {
+ return rv;
+ }
+ } else if (strncmp(str, aci_targetattr,targetattrlen ) == 0) {
+ type = ACI_TARGET_ATTR;
+
+ if ( (s = strstr( str, "!=" )) != NULL ) {
+ type |= ACI_TARGET_ATTR_NOT;
+ strncpy(s, " ", 1);
+ }
+ /* Get individual components of the targetattr.
+ * (targetattr = "cn || u* || phone ||tel:add:(tel=1234)
+ * || sn:del:(gn=5678)")
+ * If it contains a value filter, the type will also be
+ * ACI_TARGET_VALUE_ATTR.
+ */
+ if ( 0 != (rv= __aclp__init_targetattr(
+ aci_item, str))) {
+ return rv;
+ }
+ } else if (strncmp(str, aci_targetfilter,tfilterlen ) == 0) {
+ if ( aci_item->targetFilter)
+ return ACL_SYNTAX_ERR;
+
+ type = ACI_TARGET_FILTER;
+ /* we need to remove the targetfilter stuff*/
+ if ( (s = strstr( str, "!=" )) != NULL ) {
+ type |= ACI_TARGET_FILTER_NOT;
+ }
+
+ /*
+ * If it's got a macro in the targetfilter then it must
+ * have a target and it must have a macro.
+ */
+
+ if ((s = strstr (str, ACL_RULE_MACRO_DN_KEY)) != NULL ||
+ ((s = strstr(str, ACL_RULE_MACRO_DN_LEVELS_KEY)) != NULL)) {
+
+ /* Must have a targetmacro */
+ if ( !(aci_item->aci_type & ACI_TARGET_MACRO_DN)) {
+ slapi_log_error(SLAPI_LOG_FATAL, plugin_name,
+ "acl_parse: A macro in a targetfilter ($dn) must have a macro in the target.\n");
+ return(ACL_SYNTAX_ERR);
+ }
+
+ type|= ACI_TARGET_FILTER_MACRO_DN;
+ }
+
+ tmpstr = strchr(str, '=');
+ tmpstr++;
+ __acl_strip_leading_space(&tmpstr);
+
+ /*
+ * Trim off enclosing quotes and enclosing
+ * superfluous brackets.
+ * The result has been duped so it can be kept.
+ */
+
+ tmpstr = __acl_trim_filterstr( tmpstr );
+
+ f = slapi_str2filter(tmpstr);
+
+ /* save the filter string */
+ aci_item->targetFilterStr = tmpstr;
+
+ } else if (strncmp(str, aci_targetdn, targetdnlen) == 0) {
+ char *tstr = NULL;
+ const size_t LDAP_URL_prefix_len = strlen (LDAP_URL_prefix);
+ char *tt;
+ type = ACI_TARGET_DN;
+ /* Keep a copy of the target attr */
+ if (aci_item->target) {
+ return (ACL_SYNTAX_ERR);
+ }
+ if ( (s = strstr( str, "!=" )) != NULL ) {
+ type |= ACI_TARGET_NOT;
+ strncpy(s, " ", 1);
+ }
+
+ /* Convert it to lower as slapi_dn_normalize() does not */
+ for (tt = str; *tt; tt++) *tt = TOLOWER ( *tt );
+
+ if ( (s = strchr( str, '=' )) != NULL ) {
+ value = s + 1;
+ slapi_dn_normalize(value);
+ len = strlen ( value );
+ if (*value == '"' && value[len-1] == '"'){
+ value[len-1] = '\0';
+ value++;
+ }
+ __acl_strip_leading_space(&value);
+ } else {
+ return ( ACL_SYNTAX_ERR );
+ }
+
+ if ( strncasecmp ( value, LDAP_URL_prefix , LDAP_URL_prefix_len) )
+ return ( ACL_SYNTAX_ERR );
+
+ value += LDAP_URL_prefix_len;
+ len = strlen ( value );
+ tstr = (char *) slapi_ch_malloc ( targetdnlen + len + 4 );
+ sprintf ( tstr, "(target=%s)", value);
+ if ( (rv = acl_check_for_target_macro( aci_item, value)) == -1) {
+ slapi_ch_free ( (void **) &tstr );
+ return(ACL_SYNTAX_ERR);
+ } else if ( rv > 0) {
+ /* is present, so the type is now ACL_TARGET_MACRO_DN */
+ type = ACI_TARGET_MACRO_DN;
+ } else {
+ /* it's a normal target with no macros inside */
+ f = slapi_str2filter ( tstr );
+ }
+ slapi_ch_free ( (void **) &tstr );
+ } else {
+ /* did start with a 't' but was not a recognsied keyword */
+ return(ACL_SYNTAX_ERR);
+ }
+
+ /*
+ * Here, it was a recognised keyword that started with 't'.
+ * Check that the filter associated with ACI_TARGET_DN and
+ * ACI_TARGET_FILTER are OK.
+ */
+ if (f == NULL) {
+ /* The following types require a filter to have been created */
+ if (type & ACI_TARGET_DN)
+ return ACL_TARGET_FILTER_ERR;
+ else if (type & ACI_TARGET_FILTER)
+ return ACL_TARGETFILTER_ERR;
+ } else {
+ int filterChoice;
+
+ filterChoice = slapi_filter_get_choice ( f );
+ if ( (type & ACI_TARGET_DN) &&
+ ( filterChoice == LDAP_FILTER_PRESENT)) {
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+ "acl__parse_aci: Unsupported filter type:%d\n", filterChoice);
+ return(ACL_SYNTAX_ERR);
+ } else if (( filterChoice == LDAP_FILTER_SUBSTRINGS) &&
+ (type & ACI_TARGET_DN)) {
+ type &= ~ACI_TARGET_DN;
+ type |= ACI_TARGET_PATTERN;
+ }
+ }
+
+ if ((type & ACI_TARGET_DN) ||
+ (type & ACI_TARGET_PATTERN)) {
+ if (aci_item->target) {
+ /* There is something already. ERROR */
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+ "Multiple targets in the ACL syntax\n",
+ 0,0,0);
+ slapi_filter_free(f, 1);
+ return(ACL_SYNTAX_ERR);
+ } else {
+ aci_item->target = f;
+ }
+ } else if ( type & ACI_TARGET_FILTER) {
+ if (aci_item->targetFilter) {
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+ "Multiple target Filters in the ACL Syntax\n",
+ 0,0,0);
+ slapi_filter_free(f, 1);
+ return(ACL_SYNTAX_ERR);
+ } else {
+ aci_item->targetFilter = f;
+ }
+ }
+ break; /* 't' */
+ default:
+ /* Here the keyword did not start with 'v' ot 't' so error */
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+ "Unknown keyword at \"%s\"\n Expecting"
+ " \"target\", \"targetattr\", \"targetfilter\", \"targattrfilters\""
+ " or \"version\"\n", str, 0, 0);
+ return(ACL_SYNTAX_ERR);
+ }/* switch() */
+
+ /* Store the type info */
+ aci_item->aci_type |= type;
+
+ return 0;
+}
+
+/***************************************************************************
+* acl__sanity_check_acltxt
+*
+* Check the input ACL text. Reports any errors. Also forgivs if certain
+* things are missing.
+*
+* Input:
+* char *str - String containg the acl text
+* int *err - error status
+*
+* Returns:
+* 0 --- good status
+* <0 --- error
+*
+* Error Handling:
+* None.
+*
+*
+**************************************************************************/
+static int
+__aclp__sanity_check_acltxt (aci_t *aci_item, char *str)
+{
+ NSErr_t errp;
+ char *s;
+ ACLListHandle_t *handle = NULL;
+ char *newstr = NULL;
+ char *word;
+ char *next;
+
+ memset (&errp, 0, sizeof(NSErr_t));
+ newstr = str;
+
+ while ((s = strstr(newstr, "authenticate")) != NULL) {
+ char *next;
+ next = s + 12;
+ s--;
+ while (s != str && ldap_utf8isspace(s)) LDAP_UTF8DEC(s);
+ if (s && *s == ';') {
+ /* We don't support authenticate stuff */
+ return ACL_INVALID_AUTHORIZATION;
+
+ } else {
+ newstr = next;
+ }
+ }
+
+ newstr = slapi_ch_strdup (str);
+ word = ldap_utf8strtok_r(newstr, " ", &next);
+ if (strcasecmp (word, "version") == 0) {
+ word = ldap_utf8strtok_r(NULL, " ", &next);
+ if (atoi(word) != 3) {
+ slapi_ch_free ( (void **) &newstr );
+ return ACL_INCORRECT_ACI_VERSION;
+ }
+ }
+ slapi_ch_free ( (void **) &newstr );
+
+ /* We need to normalize the DNs in the userdn and group dn
+ ** so that, it's only done once.
+ */
+ if ((newstr = __aclp__normalize_acltxt (aci_item, str )) == NULL) {
+ return ACL_SYNTAX_ERR;
+ }
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name, "Normalized String:%s\n", newstr, 0,0);
+
+ /* check for acl syntax error */
+ if ((handle = (ACLListHandle_t *) ACL_ParseString(&errp,
+ newstr)) == NULL) {
+ acl_print_acllib_err(&errp, str);
+ slapi_ch_free ( (void **) &newstr );
+ return ACL_SYNTAX_ERR;
+ } else {
+ /* get the rights and the aci type */
+ aci_item->aci_handle = handle;
+ nserrDispose(&errp);
+ slapi_ch_free ( (void **) &newstr );
+
+ return 0;
+ }
+}
+/******************************************************************************
+*
+* acl__normalize_acltxt
+*
+*
+* XXXrbyrne this routine should be re-written when someone eventually
+* gets sick enough of it. Same for getNextLAS() below.
+*
+* Normalize the acltxt i.e normalize all the DNs specified in the
+* Userdn and Groupdn rule so that we normalize once here and not
+* over and over again at the runtime in the LASes. We have to normalize
+* before we generate the handle otherwise it's of no use.
+* Also convert deny to deny absolute
+*
+* The string that comes in is something like:
+* version 3.0; acl "Dept domain administration"; allow (all)
+* groupdn = "ldap:///cn=Domain Administrators, o=$dn.o, o=ISP"; )
+*
+* Returns NULL on error.
+*
+******************************************************************************/
+static char *
+__aclp__normalize_acltxt ( aci_t * aci_item, char * str )
+{
+
+ char *s, *p;
+ char *end;
+ char *aclstr, *s_aclstr;
+ char *ret_str = NULL;
+ int len;
+ char *ptr, *aclName;
+ char *nextACE;
+ char *tmp_str = NULL;
+ char *acestr = NULL;
+ char *s_acestr = NULL;
+ int aci_rights_val = 0; /* bug 389975 */
+
+ /* make a copy first */
+ s_aclstr = aclstr = slapi_ch_strdup ( str );
+
+ /* The rules are like this version 3.0; acl "xyz"; rule1; rule2; */
+ s = strchr (aclstr, ';');
+ if ( NULL == s) {
+ slapi_ch_free ( (void **) &s_aclstr );
+ return NULL;
+ }
+ aclstr = ++s;
+
+ /* From DS 4.0, we support both aci (or aci) "name" -- we have to change to acl
+ ** as libaccess will not like it
+ */
+ s = aclstr;
+ while (s && ldap_utf8isspace(s)) LDAP_UTF8INC(s);
+ *(s+2 ) = 'l';
+
+ aclName = s+3;
+
+ s = strchr (aclstr, ';');
+ if ( NULL == s) {
+ slapi_ch_free ( (void **) &s_aclstr );
+ return NULL;
+ }
+
+ aclstr = s;
+ LDAP_UTF8INC(aclstr);
+ *s = '\0';
+
+ /* Here aclName is the acl description string */
+ aci_item->aclName = slapi_ch_strdup ( aclName );
+
+ aclutil_str_appened (&ret_str, s_aclstr);
+ aclutil_str_appened (&ret_str, ";");
+
+ /* start with the string */
+ acestr = aclstr;
+
+ /*
+ * Here acestr is something like:
+ *
+ * " allow (all) groupdn = "ldap:///cn=Domain Administrators, o=$dn.o, o=ISP";)"
+ *
+ *
+ */
+
+normalize_nextACERule:
+
+ /* now we are in the rule part */
+ tmp_str = acestr;
+ s = strchr (tmp_str, ';');
+ if ( s == NULL) {
+ if (ret_str) slapi_ch_free ( (void **) &ret_str );
+ slapi_ch_free ( (void **) &s_aclstr );
+ return NULL;
+ }
+ nextACE = s;
+ LDAP_UTF8INC(nextACE);
+ *s = '\0';
+
+ /* acestr now will hold copy of the ACE. Also add
+ ** some more space in case we need to add "absolute"
+ ** for deny rule. We will never need more 2 times
+ ** the len.
+ */
+ len = strlen (tmp_str);
+ s_acestr = acestr = slapi_ch_calloc ( 1, 2 * len);
+
+ __acl_strip_leading_space(&tmp_str);
+
+ /*
+ * Now it's something like:
+ * allow (all) groupdn = "ldap:///cn=Domain Administrators, o=$dn.o, o=ISP";
+ */
+ if (strncasecmp(tmp_str, "allow", 5) == 0) {
+ memcpy(acestr, tmp_str, len);
+ tmp_str += 5;
+ /* gather the rights */
+ aci_rights_val = __aclp__get_aci_right (tmp_str);/* bug 389975 */
+ aci_item->aci_type |= ACI_HAS_ALLOW_RULE;
+
+ } else if (strncasecmp(tmp_str, "deny", 4) == 0) {
+ char *d_rule ="deny absolute";
+ /* Then we have to add "absolute" to the deny rule
+ ** What we are doing here is to tackle this situation.
+ **
+ ** allow -- deny -- allow
+ ** deny -- allow
+ **
+ ** by using deny absolute we force the precedence rule
+ ** i.e deny has a precedence over allow. Since there doesn't
+ ** seem to be an easy to detect the mix, forcing this
+ ** to all the deny rules will do the job.
+ */
+ __acl_strip_leading_space(&tmp_str);
+ tmp_str += 4;
+
+ /* We might have an absolute there already */
+ if ((s = strstr (tmp_str, "absolute")) != NULL) {
+ tmp_str = s;
+ tmp_str += 8;
+ }
+ /* gather the rights */
+ aci_rights_val = __aclp__get_aci_right (tmp_str);/* bug 389975 */
+ aci_item->aci_type |= ACI_HAS_DENY_RULE;
+
+ len = strlen ( d_rule );
+ memcpy (acestr, d_rule, len );
+ memcpy (acestr+len, tmp_str, strlen (tmp_str) );
+ } else {
+ /* wrong syntax */
+ aci_rights_val = -1 ;
+ }
+ if (aci_rights_val == -1 )
+ {
+ /* wrong syntax */
+ slapi_ch_free ( (void **) &ret_str );
+ slapi_ch_free ( (void **) &s_acestr );
+ slapi_ch_free ( (void **) &s_aclstr );
+ return NULL;
+ } else
+ aci_item->aci_access |= aci_rights_val;
+
+
+ /* Normalize all the DNs in the userdn rule */
+
+ /*
+ *
+ * Here acestr starts like this:
+ * " allow (all) groupdn = "ldap:///cn=Domain Administrators, o=$dn.o, o=ISP"
+ */
+
+ s = __aclp__getNextLASRule(aci_item, acestr, &end);
+ while ( s ) {
+ if ( 0 == strncmp ( s, DS_LAS_USERDNATTR, 10) ||
+ ( 0 == strncmp ( s, DS_LAS_USERATTR, 8))) {
+ /*
+ ** For userdnattr/userattr rule, the resources changes and hence
+ ** we cannot cache the result. See above for more comments.
+ */
+ aci_item->aci_elevel = ACI_ELEVEL_USERDNATTR;
+ } else if ( 0== strncmp ( s, DS_LAS_USERDN, 6)) {
+ p = strstr ( s, "=");
+ p--;
+ if ( strncmp (p, "!=", 2) == 0)
+ aci_item->aci_type |= ACI_CONTAIN_NOT_USERDN;
+
+ /* XXXrbyrne
+ * Here we need to scan for more ldap:/// within
+ * this userdn rule type:
+ * eg. userdn = "ldap:///cn=joe,o=sun.com || ldap:///self"
+ * This is handled correctly in DS_LASUserDnEval
+ * but the bug here is not setting ACI_USERDN_SELFRULE
+ * which would ensure that acl info is not cached from
+ * one resource entry to the next. (bug 558519)
+ */
+ p = strstr ( p, "ldap");
+ if (p == NULL) {
+ /* must start with ldap */
+ if (s_acestr) slapi_ch_free ( (void **) &s_acestr );
+ if (ret_str) slapi_ch_free ( (void **) &ret_str );
+ slapi_ch_free ( (void **) &s_aclstr );
+ return (NULL);
+ }
+ p += 8; /* for ldap:/// */
+ if( __aclp__dn_normalize (p, end) == NULL) {
+ if (s_acestr) slapi_ch_free ( (void **) &s_acestr );
+ if (ret_str) slapi_ch_free ( (void **) &ret_str );
+ slapi_ch_free ( (void **) &s_aclstr );
+ return (NULL);
+ }
+
+ /* we have a rule like userdn = "ldap:///blah". s points to blah now.
+ ** let's find if we have a SELF rule like userdn = "ldap:///self".
+ ** Since the resource changes on entry basis, we can't cache the
+ ** evalation of handle for all time. The cache result is valid
+ ** within the evaluation of that resource.
+ */
+ if (strncasecmp(p, "self", 4) == 0) {
+ aci_item->aci_ruleType |= ACI_USERDN_SELFRULE;
+ } else if ( strncasecmp(p, "anyone", 6) == 0 ) {
+ aci_item->aci_elevel = ACI_ELEVEL_USERDN_ANYONE;
+
+ } else if ( strncasecmp(p, "all", 3) == 0 ) {
+ if ( aci_item->aci_elevel > ACI_ELEVEL_USERDN_ALL )
+ aci_item->aci_elevel = ACI_ELEVEL_USERDN_ALL;
+
+ } else {
+ if ( aci_item->aci_elevel > ACI_ELEVEL_USERDN )
+ aci_item->aci_elevel = ACI_ELEVEL_USERDN;
+ }
+
+ /* See if we have a parameterized rule */
+ __aclp_chk_paramRules ( aci_item, p, end );
+ } else if ( 0 == strncmp ( s, DS_LAS_GROUPDNATTR, 11)) {
+ /*
+ ** For groupdnattr rule, the resources changes and hence
+ ** we cannot cache the result. See above for more comments.
+ */
+ /* Find out if we have a URL type of rule */
+ if ((p= strstr (s, "ldap")) != NULL) {
+ if ( aci_item->aci_elevel > ACI_ELEVEL_GROUPDNATTR_URL )
+ aci_item->aci_elevel = ACI_ELEVEL_GROUPDNATTR_URL;
+ } else if ( aci_item->aci_elevel > ACI_ELEVEL_GROUPDNATTR ) {
+ aci_item->aci_elevel = ACI_ELEVEL_GROUPDNATTR;
+ }
+ aci_item->aci_ruleType |= ACI_GROUPDNATTR_RULE;
+ } else if ( 0 == strncmp ( s, DS_LAS_GROUPDN, 7)) {
+
+ p = strstr ( s, "=");
+ p--;
+ if ( strncmp (p, "!=", 2) == 0)
+ aci_item->aci_type |= ACI_CONTAIN_NOT_GROUPDN;
+
+ p = strstr ( s, "ldap");
+ if (p == NULL) {
+ /* must start with ldap */
+ if (s_acestr) slapi_ch_free ( (void **) &s_acestr );
+ if (ret_str) slapi_ch_free ( (void **) &ret_str );
+ slapi_ch_free ( (void **) &s_aclstr );
+ return (NULL);
+ }
+ p += 8;
+ if (__aclp__dn_normalize (p, end) == NULL) {
+ if (s_acestr) slapi_ch_free ( (void **) &s_acestr );
+ if (ret_str) slapi_ch_free ( (void **) &ret_str );
+ slapi_ch_free ( (void **) &s_aclstr );
+ return (NULL);
+ }
+ /* check for param rules */
+ __aclp_chk_paramRules ( aci_item, p, end );
+
+ if ( aci_item->aci_elevel > ACI_ELEVEL_GROUPDN )
+ aci_item->aci_elevel = ACI_ELEVEL_GROUPDN;
+ aci_item->aci_ruleType |= ACI_GROUPDN_RULE;
+
+ } else if ( 0 == strncmp ( s, DS_LAS_ROLEDN, 6)) {
+
+ p = strstr ( s, "=");
+ p--;
+ if ( strncmp (p, "!=", 2) == 0)
+ aci_item->aci_type |= ACI_CONTAIN_NOT_ROLEDN;
+
+ p = strstr ( s, "ldap");
+ if (p == NULL) {
+ /* must start with ldap */
+ if (s_acestr) slapi_ch_free ( (void **) &s_acestr );
+ if (ret_str) slapi_ch_free ( (void **) &ret_str );
+ slapi_ch_free ( (void **) &s_aclstr );
+ return (NULL);
+ }
+ p += 8;
+ if (__aclp__dn_normalize (p, end) == NULL) {
+ if (s_acestr) slapi_ch_free ( (void **) &s_acestr );
+ if (ret_str) slapi_ch_free ( (void **) &ret_str );
+ slapi_ch_free ( (void **) &s_aclstr );
+ return (NULL);
+ }
+ /* check for param rules */
+ __aclp_chk_paramRules ( aci_item, p, end );
+
+ /* XXX need this for roledn ?
+ if ( aci_item->aci_elevel > ACI_ELEVEL_GROUPDN )
+ aci_item->aci_elevel = ACI_ELEVEL_GROUPDN;*/
+ aci_item->aci_ruleType |= ACI_ROLEDN_RULE;
+ }
+ s = ++end;
+ s = __aclp__getNextLASRule(aci_item, s, &end);
+ }/* while */
+
+ /* get the head of the string */
+ acestr = s_acestr;
+ len = strlen( acestr);
+ ptr = acestr +len-1;
+ while (*ptr && *ptr != '\"' && *ptr != ')' ) *ptr-- = ' ';
+ ptr++;
+ *ptr = ';';
+
+ aclutil_str_appened (&ret_str, acestr);
+ if (s_acestr) {
+ slapi_ch_free ( (void **) &s_acestr );
+ }
+ s_acestr = NULL;
+
+ if (nextACE) {
+ s = strstr (nextACE, "allow");
+ if (s == NULL) s = strstr (nextACE, "deny");
+ if (s == NULL) {
+ if (nextACE && *nextACE != '\0')
+ aclutil_str_appened (&ret_str, nextACE);
+ slapi_ch_free ( (void **) &s_aclstr );
+ return (ret_str);
+ }
+ acestr = nextACE;
+ goto normalize_nextACERule;
+ }
+
+ slapi_ch_free ( (void **) &s_aclstr );
+ return (ret_str);
+}
+/*
+ *
+ * acl__getNextLASRule
+ * Find the next rule.
+ *
+ * Returns:
+ * endOfCurrRule - end of current rule
+ * nextRule - start of next rule
+ */
+static char *
+__aclp__getNextLASRule (aci_t *aci_item, char *original_str , char **endOfCurrRule)
+{
+ char *newstr, *word, *next, *start, *end;
+ char *ruleStart = NULL;
+ int len, ruleLen;
+ int in_dn_expr = 0;
+
+ *endOfCurrRule = NULL;
+ end = start = NULL;
+
+ newstr = slapi_ch_strdup (original_str);
+
+ if ( (strncasecmp(newstr, "allow", 5) == 0) ||
+ (strncasecmp(newstr, "deny", 4) == 0) ) {
+ word = ldap_utf8strtok_r(newstr, ")", &next);
+ }
+ else {
+ word = ldap_utf8strtok_r(newstr, " ", &next);
+ }
+
+ /*
+ * The first word is of no interest -- skip it
+ * it's allow or deny followed by the rights (<rights>),
+ * so skip over the rights as well or it's 'and', 'or',....
+ */
+
+ while ( (word = ldap_utf8strtok_r(NULL, " ", &next)) != NULL) {
+ int got_rule = 0;
+ int ruleType = 0;
+ /*
+ ** The next word must be one of these to be considered
+ ** a valid rule.
+ ** This is making me crazy. We might have a case like
+ ** "((userdn=". strtok is returning me that word.
+ */
+ len = strlen ( word );
+ word [len] = '\0';
+
+ if ( (ruleStart= strstr(word, DS_LAS_USERDNATTR)) != NULL) {
+ ruleType |= ACI_USERDNATTR_RULE;
+ ruleLen = strlen ( DS_LAS_USERDNATTR) ;
+ } else if ( (ruleStart = strstr(word, DS_LAS_USERDN)) != NULL) {
+ ruleType = ACI_USERDN_RULE;
+ ruleLen = strlen ( DS_LAS_USERDN);
+ in_dn_expr = 1;
+ } else if ( (ruleStart = strstr(word, DS_LAS_GROUPDNATTR)) != NULL) {
+ ruleType = ACI_GROUPDNATTR_RULE;
+ ruleLen = strlen ( DS_LAS_GROUPDNATTR) ;
+ } else if ((ruleStart= strstr(word, DS_LAS_GROUPDN)) != NULL) {
+ ruleType = ACI_GROUPDN_RULE;
+ ruleLen = strlen ( DS_LAS_GROUPDN) ;
+ in_dn_expr = 1;
+ } else if ((ruleStart = strstr(word, DS_LAS_USERATTR)) != NULL) {
+ ruleType = ACI_USERATTR_RULE;
+ ruleLen = strlen ( DS_LAS_USERATTR) ;
+ } else if ((ruleStart= strstr(word, DS_LAS_ROLEDN)) != NULL) {
+ ruleType = ACI_ROLEDN_RULE;
+ ruleLen = strlen ( DS_LAS_ROLEDN);
+ in_dn_expr = 1;
+ } else if ((ruleStart= strstr(word, DS_LAS_AUTHMETHOD)) != NULL) {
+ ruleType = ACI_AUTHMETHOD_RULE;
+ ruleLen = strlen ( DS_LAS_AUTHMETHOD);
+ } else if ((ruleStart = strstr(word, ACL_ATTR_IP)) != NULL) {
+ ruleType = ACI_IP_RULE;
+ ruleLen = strlen ( ACL_ATTR_IP) ;
+ } else if ((ruleStart = strstr(word, DS_LAS_TIMEOFDAY)) != NULL) {
+ ruleType = ACI_TIMEOFDAY_RULE;
+ ruleLen = strlen ( DS_LAS_TIMEOFDAY) ;
+ } else if ((ruleStart = strstr(word, DS_LAS_DAYOFWEEK)) != NULL) {
+ ruleType = ACI_DAYOFWEEK_RULE;
+ ruleLen = strlen ( DS_LAS_DAYOFWEEK) ;
+ } else if ((ruleStart = strstr(word, ACL_ATTR_DNS)) != NULL) {
+ ruleType = ACI_DNS_RULE;
+ ruleLen = strlen ( ACL_ATTR_DNS) ;
+ }
+ /* Here, we've found a space...if we were in in_dn_expr mode
+ * and we'vve found a closure for that ie.a '"' or a ')'
+ * eg. "'ldap:///all"' or 'ldap:///all")' then exit in_dn_expr mode.
+ */
+ if ( in_dn_expr && (word[len-1] == '"' ||
+ len>1 && word[len-2] == '"' ||
+ len>2 && word[len-3] == '"')) {
+ in_dn_expr = 0;
+ }
+
+ /*
+ * ruleStart may be NULL as word could be (all) for example.
+ * this word will just be skipped--we're really waiting for
+ * userdn or groupdn or...
+ */
+
+ if ( ruleStart && ruleType ) {
+ /* Look in the current word for "=" or else look into
+ ** the next word -- if none of them are true, then this
+ ** is not the start of the rule
+ */
+ char *tmpStr = ruleStart + ruleLen;
+ if ( strchr ( tmpStr, '=') ||
+ ((word = ldap_utf8strtok_r(NULL, " ", &next) ) &&
+ word && ((strncmp ( word, "=", 1) == 0 ) ||
+ (strncmp ( word, "!=",2) ==0) ||
+ (strncmp ( word, ">", 1) == 0 ) ||
+ (strncmp ( word, "<", 1) == 0 ) ||
+ (strncmp ( word, "<", 1) == 0 ) ||
+ (strncmp ( word, "<=",2) ==0 ) ||
+ (strncmp ( word, ">=",2) ==0) ||
+ (strncmp ( word, "=>",2) ==0) ||
+ (strncmp ( word, "=<",2) ==0))
+ ) ){
+ aci_item->aci_ruleType |= ruleType;
+ got_rule = 1;
+ }
+ }
+ if ( NULL == start && got_rule ) {
+ /*
+ * We've just found a rule start--keep going though because
+ * we need to return the end of this rule too.
+ */
+ start= ruleStart;
+ got_rule = 0;
+ } else {
+ /*
+ * Here, we have a candidate for the end of the rule we've found
+ * (the start of which is currently in start).
+ * But we need to be sure it really is the end and not a
+ * "fake end" due to a keyword bbeing embeded in a dn.
+ */
+ if (word && !in_dn_expr &&
+ ((strcasecmp(word, "and") == 0) ||
+ (strcasecmp(word, "or") == 0) ||
+ (strcasecmp(word, "not") == 0) ||
+ (strcasecmp(word, ";") == 0))) {
+ /* If we have start, then it really is the end */
+ word--;
+ if (start) {
+ end = word;
+ break;
+ } else {
+ /* We found a fake end, but we've no start so keep going */
+ }
+ }
+ }
+ } /* while */
+
+
+ if ( end ) {
+ /* Found an end to the rule and it's not the last rule */
+ len = end - newstr;
+ end = original_str +len;
+ while ( (end != original_str) && *end != '\"') end--;
+ *endOfCurrRule = end;
+ len = start - newstr;
+ ruleStart = original_str + len;
+ } else {
+ /* Walked off the end of the string so it's the last rule */
+ end = original_str + strlen(original_str)-1;
+ while ( (end != original_str) && *end != '\"') end--;
+ *endOfCurrRule = end;
+ }
+ if ( start ) {
+ /* Got a rule, fixup the pointer */
+ len = start - newstr;
+ ruleStart = original_str + len;
+ }
+ slapi_ch_free ( (void **) &newstr );
+
+ /*
+ * Here, ruleStart points to the start of the next rule in original_str.
+ * end points to the end of this rule.
+ */
+
+ return ( ruleStart );
+}
+/******************************************************************************
+*
+* __aclp__dn_normalize
+*
+* Normalize the DN INPLACE. This routine is similar to slapi_dn_normalize()
+* except various small stuff at the end.
+* Normalize until the "end" and not to the end of string.
+*
+******************************************************************************/
+static char *
+__aclp__dn_normalize( char *dn , char *end)
+{
+ char *d;
+
+ if ((end - dn) < 0) {
+ return(NULL);
+ }
+
+ d = slapi_dn_normalize_to_end ( dn, end );
+
+ /* Do I have the quotes already */
+ if (*d != '\"' ) {
+ /*
+ ** We are taking care of this situation
+ ** " ") ". We need to remove the space
+ ** infront and tack it after the quote like this.
+ ** "" ) ".
+ */
+
+ *d = '\"';
+ d++;
+ while (*d && *d != '\"') *d++ = ' ';
+ *d = ' ';
+ }
+
+ return( dn );
+}
+/***************************************************************************
+* acl__get_aci_right
+*
+* Go thru the one acl text str and figure our the rights declared.
+*
+*****************************************************************************/
+static int
+__aclp__get_aci_right (char *str)
+{
+
+ char *sav_str = slapi_ch_strdup(str);
+ char *t, *tt;
+ int type = 0;
+ char *delimiter = ",";
+ char *val = NULL;
+ int aclval = 0;
+
+ t = sav_str;
+ __acl_strip_leading_space( &t );
+
+ if (*t == '(' ) {
+ if ((tt = slapi_find_matching_paren(t)) == NULL) {
+ slapi_ch_free ( (void **) &sav_str );
+ return -1;
+ } else {
+ t++; /* skip the first character which is ( */
+ *tt = '\0';
+ }
+ } else {
+ slapi_ch_free ( (void **) &sav_str );
+ return -1;
+ }
+ /* get the tokens separated by "," */
+ val = ldap_utf8strtok_r(t,delimiter, &tt);
+ if (val == NULL )
+ {
+ slapi_ch_free ( (void **) &sav_str );
+ return -1;
+ }
+ while (val != NULL)
+ {
+ /* get the corresponding integer value */
+ aclval = get_acl_rights_as_int(val);
+ if (aclval == -1 )
+ {
+ type = -1;
+ break;
+ }
+ type |= aclval;
+ val = ldap_utf8strtok_r(NULL,delimiter, &tt); /* get the next token */
+ }
+
+ slapi_ch_free ( (void **) &sav_str );
+ return type;
+
+}
+
+static int get_acl_rights_as_int( char * strValue)
+{
+
+ if (strValue == NULL )
+ return -1;
+ /* First strip out the leading and trailing spaces */
+ __acl_strip_leading_space( &strValue );
+ __acl_strip_trailing_space( strValue );
+
+ /* We have to do a strcasecmp (case insensitive cmp) becuase we should return
+ only if it is exact match. */
+
+ if (strcasecmp (strValue, "read") == 0 )
+ return SLAPI_ACL_READ;
+ else if (strcasecmp (strValue, "write") == 0 )
+ return SLAPI_ACL_WRITE;
+ else if (strcasecmp (strValue, "search") == 0 )
+ return SLAPI_ACL_SEARCH;
+ else if (strcasecmp (strValue, "compare") == 0 )
+ return SLAPI_ACL_COMPARE;
+ else if (strcasecmp (strValue, "add") == 0 )
+ return SLAPI_ACL_ADD;
+ else if (strcasecmp (strValue, "delete") == 0 )
+ return SLAPI_ACL_DELETE;
+ else if (strcasecmp (strValue, "proxy") == 0 )
+ return SLAPI_ACL_PROXY;
+ else if (strcasecmp (strValue, "selfwrite") == 0 )
+ return (SLAPI_ACL_SELF | SLAPI_ACL_WRITE);
+ else if (strcasecmp (strValue, "all") == 0 )
+ return SLAPI_ACL_ALL;
+ else
+ return -1; /* error */
+}
+/***************************************************************************
+*
+* acl_access2str
+*
+* Convert the access bits into character strings.
+* Example: "read, self read"
+*
+* Input:
+*
+* int access - The access in bits
+* char **rights - rights in chars
+*
+* Returns:
+* NULL - No rights to start with
+* right - rights converted.
+*
+* Error Handling:
+* None.
+*
+**************************************************************************/
+char *
+acl_access2str(int access)
+{
+
+ if ( access & SLAPI_ACL_COMPARE ) {
+ return access_str_compare;
+ } else if ( access & SLAPI_ACL_SEARCH ) {
+ return access_str_search;
+ } else if ( access & SLAPI_ACL_READ ) {
+ return access_str_read;
+ } else if ( access & SLAPI_ACL_DELETE) {
+ return access_str_delete;
+ } else if ( access & SLAPI_ACL_ADD) {
+ return access_str_add;
+ } else if ( (access & SLAPI_ACL_WRITE ) && (access & SLAPI_ACL_SELF)) {
+ return access_str_selfwrite;
+ } else if (access & SLAPI_ACL_WRITE ) {
+ return access_str_write;
+ } else if (access & SLAPI_ACL_PROXY ) {
+ return access_str_proxy;
+ }
+
+ return NULL;
+}
+/***************************************************************************
+*
+* __aclp__init_targetattr
+*
+* Parse the targetattr string and create a array of attrs. This will
+* help us to do evaluation at run time little faster.
+* entry.
+* Here, also extract any target value filters.
+*
+* Input:
+* aci_t *aci -- The aci item
+* char *str -- the targetattr string
+*
+* Returns:
+* ACL_OK - everything ok
+* ACL_SYNTAX_ERROR - in case of error.
+*
+*
+***************************************************************************/
+static int
+__aclp__init_targetattr (aci_t *aci, char *attr_val)
+{
+
+ int numattr=0;
+ Targetattr **attrArray;
+ char *s, *end_attr, *str;
+ int len;
+ Targetattr *attr = NULL;
+
+ s = strchr (attr_val, '=');
+ s++;
+ __acl_strip_leading_space(&s);
+ len = strlen(s);
+ if (*s == '"' && s[len-1] == '"') {
+ s[len-1] = '\0';
+ s++;
+ }
+
+ str = s;
+ attrArray = aci->targetAttr;
+
+ if (attrArray[0] != NULL) {
+ /*
+ ** That means we are visiting more than once.
+ ** Syntax error. We have a case like: (targetattr) (targetattr)
+ */
+ return ACL_SYNTAX_ERR;
+ }
+
+ while (str != 0 && *str != 0) {
+
+ __acl_strip_leading_space(&str);
+
+ if ((end_attr = strstr(str, "||")) != NULL) {
+ /* skip the two '|' chars */
+ auto char *t = end_attr;
+ LDAP_UTF8INC(end_attr);
+ LDAP_UTF8INC(end_attr);
+ *t = 0;
+ }
+ __acl_strip_trailing_space(str);
+
+ /*
+ * Here:
+ * end_attr points to the next attribute thing.
+ *
+ * str points to the current one to be processed and it looks like this:
+ * rbyrneXXX Watchout is it OK to use : as the speperator ?
+ * cn
+ * c*n*
+ * *
+ * The attribute goes in the attrTarget list.
+ *
+ */
+
+
+ attr = (Targetattr *) slapi_ch_malloc (sizeof (Targetattr));
+ memset (attr, 0, sizeof(Targetattr));
+
+ if (strchr(str, '*')) {
+
+ /* It contains a * so it's something like * or cn* */
+ if (strcmp(str, "*" ) != 0) {
+ char line[100];
+ char *lineptr = &line[0];
+ char *newline = NULL;
+ int lenstr = 0;
+ struct slapi_filter *f = NULL;
+
+ if ((lenstr = strlen(str)) > 91) { /* 100 - 8 for "(attr =%s)" */
+ newline = slapi_ch_malloc(lenstr + 9);
+ lineptr = newline;
+ }
+
+ attr->attr_type = ACL_ATTR_FILTER;
+ sprintf (lineptr, "(attr =%s)", str);
+ f = slapi_str2filter (lineptr);
+
+ if (f == NULL) {
+ slapi_log_error(SLAPI_LOG_FATAL, plugin_name,
+ "__aclp__init_targetattr:Unable to generate filter (%s)\n", lineptr,0,0);
+ } else {
+ attr->u.attr_filter = f;
+ }
+
+ if (newline) slapi_ch_free((void **) &newline);
+ } else {
+ attr->attr_type = ACL_ATTR_STAR;
+ attr->u.attr_str = slapi_ch_strdup (str);
+ }
+
+ } else {
+ attr->u.attr_str = slapi_ch_strdup (str);
+ attr->attr_type = ACL_ATTR_STRING;
+ }
+
+
+ /*
+ * Add the attr to the targetAttr list
+ */
+
+ attrArray[numattr] = attr;
+ numattr++;
+ if (!(numattr % ACL_INIT_ATTR_ARRAY)) {
+ aci->targetAttr = (Targetattr **) slapi_ch_realloc (
+ (void *) aci->targetAttr,
+ (numattr+ACL_INIT_ATTR_ARRAY) *
+ sizeof(Targetattr *));
+ attrArray = aci->targetAttr;
+ }
+
+
+ /* Move on to the next attribute in the list */
+ str = end_attr;
+
+ } /* while */
+
+ /* NULL teminate the list */
+ attrArray[numattr] = NULL;
+ return 0;
+}
+
+void
+acl_strcpy_special (char *d, char *s)
+{
+ for (; *s; LDAP_UTF8INC(s)) {
+ switch (*s) {
+ case '.':
+ case '\\':
+ case '[':
+ case ']':
+ case '*':
+ case '+':
+ case '^':
+ case '$':
+ *d = '\\';
+ LDAP_UTF8INC(d);
+ /* FALL */
+ default:
+ d += LDAP_UTF8COPY(d, s);
+ }
+ }
+ *d = '\0';
+}
+/***************************************************************************
+*
+* acl_verify_aci_syntax
+* verify if the aci's being added for the entry has a valid syntax or not.
+*
+* Input:
+* Slapi_Entry *e - The Slapi_Entry itself
+* char **errbuf; -- error message
+*
+* Returns:
+* -1 (ACL_ERR) - Syntax error
+* 0 - No error
+*
+* Error Handling:
+* None.
+*
+**************************************************************************/
+int
+acl_verify_aci_syntax (Slapi_Entry *e, char **errbuf)
+{
+
+ if (e != NULL) {
+ Slapi_DN *e_sdn;
+ int rv;
+ Slapi_Attr *attr = NULL;
+ Slapi_Value *sval=NULL;
+ const struct berval *attrVal;
+ int i;
+
+ e_sdn = slapi_entry_get_sdn ( e );
+
+ slapi_entry_attr_find (e, aci_attr_type, &attr);
+ if (! attr ) return 0;
+
+ i= slapi_attr_first_value ( attr,&sval );
+ while ( i != -1 ) {
+ attrVal = slapi_value_get_berval ( sval );
+ rv=acl_verify_syntax ( e_sdn, attrVal);
+ if ( 0 != rv ) {
+ aclutil_print_err(rv, e_sdn, attrVal, errbuf);
+ return ACL_ERR;
+ }
+ i = slapi_attr_next_value ( attr, i, &sval );
+ }
+ }
+ return(0);
+}
+/***************************************************************************
+*
+* acl__verify_syntax
+* Called from slapi_acl_check_mods() to verify if the new aci being
+* added/replaced has the right syntax or not.
+*
+* Input:
+* Slapi_DN *e_sdn - sdn of the entry
+* berval *bval - The berval containg the aci value
+*
+* Returns:
+* return values from acl__parse_aci()
+*
+* Error Handling:
+* None.
+*
+**************************************************************************/
+int
+acl_verify_syntax(const Slapi_DN *e_sdn, const struct berval *bval)
+{
+ aci_t *aci_item;
+ int rv = 0;
+ char *str;
+ aci_item = acllist_get_aci_new ();
+ slapi_sdn_set_ndn_byval ( aci_item->aci_sdn, slapi_sdn_get_ndn ( e_sdn ) );
+
+ /* make a copy the the string */
+ str = slapi_ch_strdup(bval->bv_val);
+ rv = acl_parse(str, aci_item);
+
+ /* cleanup before you leave ... */
+ acllist_free_aci (aci_item);
+ slapi_ch_free ( (void **) &str );
+ return(rv);
+}
+static void
+__aclp_chk_paramRules ( aci_t *aci_item, char *start, char *end)
+{
+
+ size_t len;
+ char *str;
+ char *p, *s;
+
+
+ len = end - start;
+
+ s = str = (char *) slapi_ch_calloc(1, len + 1);
+ memcpy ( str, start, len);
+ while ( (p= strchr ( s, '$')) != NULL) {
+ p++; /* skip the $ */
+ if ( 0 == strncasecmp ( p, "dn", 2))
+ aci_item->aci_ruleType |= ACI_PARAM_DNRULE;
+ else if ( 0 == strncasecmp ( p, "attr", 4))
+ aci_item->aci_ruleType |= ACI_PARAM_ATTRRULE;
+
+ s = p;
+ }
+ slapi_ch_free ( (void **) &str );
+}
+
+/*
+ * Check for an ocurrence of a macro aci in the target.
+ * value is the normalized target string.
+ *
+ * this is something like:
+ * (target="ldap:///cn=*,ou=people,($dn),o=sun.com")
+ *
+ *
+ * returns 1 if there is a $dn present.
+ * returns 0 if not.
+ * returns -1 is syntax error.
+ * If succes then:
+ * ACI_TARGET_MACRO_DN is the type.
+ * type can also include, ACI_TARGET_PATTERN, ACI_TARGET_NOT.
+ * Also aci_item->aci_macro->match_this is set to be
+ * cn=*,ou=people,($dn),o=sun.com, to be used later.
+ *
+ * . we allow at most one ($dn) in a target.
+ * . if a "*" accurs with it, it must be the first component and at most
+ * once.
+ * . it's ok for ($dn) to occur on it's own in a target, but if it appears in
+ * a user rule, then it must be in the target.
+ *
+ *
+ *
+*/
+
+static int
+acl_check_for_target_macro( aci_t *aci_item, char *value)
+{
+
+ char *str = NULL;
+
+ str = strstr( value, ACL_TARGET_MACRO_DN_KEY);
+
+ if (str != NULL) {
+ aci_item->aci_type &= ~ACI_TARGET_DN;
+ aci_item->aci_type |= ACI_TARGET_MACRO_DN;
+ aci_item->aci_macro = (aciMacro *)slapi_ch_malloc(sizeof(aciMacro));
+ aci_item->aci_macro->match_this = slapi_ch_strdup(value);
+ aci_item->aci_macro->macro_ptr = strstr( aci_item->aci_macro->match_this,
+ ACL_TARGET_MACRO_DN_KEY);
+ return(1);
+ }
+
+ return(0);
+}
+
+/* Strip trailing spaces from str by writing '\0' into them */
+
+static void
+__acl_strip_trailing_space( char *str) {
+
+ char *ptr = NULL;
+ int len = 0;
+
+ if (*str) {
+ /* ignore trailing whitespace */
+ len = strlen(str);
+ ptr = str+len-1;
+ while(ldap_utf8isspace(ptr)){ *ptr = '\0'; LDAP_UTF8DEC(ptr); }
+ }
+}
+
+/*
+ * Strip leading spaces by resetting str to point to the first
+ * non-space charater.
+*/
+
+static void
+__acl_strip_leading_space( char **str) {
+
+ char *tmp_ptr = NULL;
+
+ tmp_ptr = *str;
+ while ( *tmp_ptr && ldap_utf8isspace( tmp_ptr ) ) LDAP_UTF8INC(tmp_ptr);
+ *str = tmp_ptr;
+}
+
+
+/*
+ * str is a string containing an LDAP filter.
+ * Trim off enclosing quotes and enclosing
+ * superfluous brackets.
+ * The result is duped so it can be kept.
+*/
+
+static char *
+__acl_trim_filterstr( char * str ) {
+
+ char *tmpstr;
+ int len;
+ char *end;
+
+ tmpstr = str;
+
+ /* If the last char is a "," take it out */
+
+ len = strlen (tmpstr);
+ if (len>0 && (tmpstr[len-1] == ',')) {
+ tmpstr [len-1] = '\0';
+ }
+
+
+ /* Does it have quotes around it */
+ len = strlen (tmpstr);
+ if (*tmpstr == '"' && tmpstr[len-1] == '"') {
+ tmpstr [len-1] = '\0';
+ tmpstr++;
+ }
+
+ str = tmpstr;
+
+ /* If we have a filter like
+ ** (((&(...) (...)))), we need to get rid of the
+ ** multiple parens or slapi_str2filter will not
+ ** evaluate properly. Need to package like
+ ** (filter ). probably I should fix str2filter
+ ** code.
+ */
+
+ while (*tmpstr++ == '(' && *tmpstr == '(') {
+ if ((end = slapi_find_matching_paren( str )) != NULL) {
+ *end = '\0';
+ str++;
+ }
+ }
+
+ return( slapi_ch_strdup(str));
+}
+
+/*
+ * Here str points to a targetattrfilters thing which looks tlike this:
+ *
+ * targetattrfilters="add=attr1:F1 && attr2:F2 ... && attrn:Fn,
+ * del=attr1:F1 && attr2:F2... && attrn:Fn")
+ *
+ *
+ */
+
+static int __acl__init_targetattrfilters( aci_t *aci, char *input_str) {
+
+ int numattr=0;
+ char *s, *str;
+ int len;
+ char *addlistptr = NULL;
+ char *dellistptr = NULL;
+
+ if (aci->targetAttrAddFilters != NULL ||
+ aci->targetAttrDelFilters != NULL) {
+
+ /*
+ ** That means we are visiting more than once.
+ ** Syntax error.
+ ** We have a case like: (targetattrfilters) (targetattrfilters)
+ */
+
+ return ACL_SYNTAX_ERR;
+ }
+
+ /* First, skip the "targetattrfilters" */
+
+ s = strchr (input_str, '=');
+ s++; /* skip the = */
+ __acl_strip_leading_space(&s); /* skip to next significant character */
+ len = strlen(s); /* Knock off the " and trailing ) */
+ if (*s == '"' && s[len-1] == '"') {
+ s[len-1] = '\0';
+ s++; /* skip the first " */
+ } else { /* No matching quotes */
+
+ return (ACL_SYNTAX_ERR);
+ }
+
+ str = s;
+
+ /*
+ * Here str looks like add=attr1:F1...attrn:Fn,
+ * del=attr1:F1...attrn:Fn
+ *
+ * extract the add and del filter lists and process each one
+ * in turn.
+ */
+
+ s = strchr (str, '=');
+ *s = '\0';
+ s++; /* skip the = */
+ __acl_strip_leading_space(&s); /* start of the first filter list */
+
+
+ /*
+ * Now str is add or del
+ * s points to the first filter list.
+ */
+
+ if (strcmp(str, "add") == 0) {
+ aci->aci_type |= ACI_TARGET_ATTR_ADD_FILTERS;
+ addlistptr = s;
+
+ /* Now isolate the first filter list. */
+ if ((str = strstr(s , "del=")) || ((str = strstr(s , "del ="))) ) {
+ str--;
+ *str = '\0';
+ *str++;
+ }
+
+
+ } else if (strcmp(str, "del") == 0) {
+ aci->aci_type |= ACI_TARGET_ATTR_DEL_FILTERS;
+ dellistptr = s;
+
+ /* Now isolate the first filter list. */
+ if ((str = strstr(s , "add=")) || ((str = strstr(s , "add ="))) ) {
+ str--;
+ *str = '\0';
+ *str++;
+ }
+ } else {
+ return(ACL_SYNTAX_ERR);
+ }
+
+ __acl_strip_trailing_space(s);
+
+ /*
+ * Here, we have isolated the first filter list.
+ * There may be a second one.
+ * Now, str points to the start of the
+ * string that contains the second filter list.
+ * If there is none then str is NULL.
+ */
+
+ if (str != NULL ){
+
+ __acl_strip_leading_space(&str);
+ s = strchr (str, '=');
+ *s = '\0';
+ s++;
+ __acl_strip_trailing_space(str);
+ __acl_strip_leading_space(&s);
+
+
+ /*
+ * s points to the start of the second filter list.
+ * str is add or del
+ */
+
+ if (aci->aci_type & ACI_TARGET_ATTR_ADD_FILTERS) {
+
+ if (strcmp(str, "del") == 0) {
+ aci->aci_type |= ACI_TARGET_ATTR_DEL_FILTERS;
+ dellistptr = s;
+ } else {
+ return(ACL_SYNTAX_ERR);
+ }
+ } else if ( aci->aci_type & ACI_TARGET_ATTR_DEL_FILTERS ) {
+ if (strcmp(str, "add") == 0) {
+ aci->aci_type |= ACI_TARGET_ATTR_ADD_FILTERS;
+ addlistptr = s;
+ } else {
+ return(ACL_SYNTAX_ERR);
+ }
+ }
+ }
+
+ /*
+ * addlistptr points to the add filter list.
+ * dellistptr points to the del filter list.
+ * In both cases the strings have been leading and trailing space
+ * stripped.
+ * Either may be NULL.
+ */
+
+ if (process_filter_list( &aci->targetAttrAddFilters, addlistptr)
+ == ACL_SYNTAX_ERR) {
+ return( ACL_SYNTAX_ERR);
+ }
+
+ if (process_filter_list( &aci->targetAttrDelFilters, dellistptr)
+ == ACL_SYNTAX_ERR) {
+ return( ACL_SYNTAX_ERR);
+ }
+
+ return(0);
+
+}
+
+/*
+ * We have a list of filters that looks like this:
+ * attr1:F1 &&....attrn:Fn
+ *
+ * We need to put each component into a targetattrfilter component of
+ * the array.
+ *
+*/
+
+static int process_filter_list( Targetattrfilter ***input_attrFilterArray,
+ char * input_str) {
+
+ char *str, *end_attr, *tmp_attr;
+ Targetattrfilter *attrfilter = NULL;
+ int numattr=0;
+ Targetattrfilter **attrFilterArray = NULL;
+
+ str = input_str;
+
+ while (str != 0 && *str != 0) {
+
+ if ((end_attr = strstr(str, "&&")) != NULL) {
+ /* skip the two '|' chars */
+ auto char *t = end_attr;
+ LDAP_UTF8INC(end_attr);
+ LDAP_UTF8INC(end_attr);
+ *t = 0;
+ }
+ __acl_strip_trailing_space(str);
+ __acl_strip_leading_space(&str);
+
+ /*
+ * Here:
+ * end_attr points to the next attribute thing.
+ *
+ * str points to the current one to be processed and it looks like
+ * this:
+ *
+ * attr1:F1
+ *
+ */
+
+ attrfilter = (Targetattrfilter *) slapi_ch_malloc (sizeof (Targetattrfilter));
+ memset (attrfilter, 0, sizeof(Targetattrfilter));
+
+ if ((tmp_attr = strstr( str,":")) != NULL) {
+
+ if ( __acl_init_targetattrfilter( attrfilter, str ) != 0 ) {
+ slapi_ch_free((void**)&attrfilter);
+ return(ACL_SYNTAX_ERR);
+ }
+ } else {
+ slapi_ch_free((void**)&attrfilter);
+ return(ACL_SYNTAX_ERR);
+ }
+
+
+ /*
+ * Add the attrfilte to the targetAttrFilter list
+ */
+
+
+ attrFilterArray = (Targetattrfilter **) slapi_ch_realloc (
+ (void *) attrFilterArray,
+ ((numattr+1)*sizeof(Targetattrfilter *)) );
+ attrFilterArray[numattr] = attrfilter;
+ numattr++;
+
+ /* Move on to the next attribute in the list */
+ str = end_attr;
+
+ }/* while */
+
+ /* NULL terminate the list */
+
+ attrFilterArray = (Targetattrfilter **) slapi_ch_realloc (
+ (void *) attrFilterArray,
+ ((numattr+1)*sizeof(Targetattrfilter *)) );
+ attrFilterArray[numattr] = NULL;
+
+ *input_attrFilterArray = attrFilterArray;
+ return 0;
+
+}
+
+/*
+ * Take str and put it into the attrfilter component.
+ *
+ * str looks as follows: attr1:F1
+ *
+ * It has had leading and trailing space stripped.
+*/
+
+static int __acl_init_targetattrfilter( Targetattrfilter *attrfilter,
+ char *str ) {
+
+ char *tmp_ptr, *s, *filter_ptr;
+ Slapi_Filter *f = NULL;
+
+ s = str;
+
+ /* First grab the attribute name */
+
+ if ( (tmp_ptr = strstr( str, ":")) == NULL ) {
+ /* No :, syntax error */
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+ "Bad targetattrfilter %s:%s\n",
+ str,"Expecting \":\"",0);
+
+ return(ACL_SYNTAX_ERR);
+ }
+ *tmp_ptr = '\0';
+ LDAP_UTF8INC(tmp_ptr);
+
+ __acl_strip_trailing_space(s);
+
+ /* s should be the attribute name-make sure it's non-empty. */
+
+ if ( *s == '\0' ) {
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+ "No attribute name in targattrfilters\n",
+ 0,0);
+ return(ACL_SYNTAX_ERR);
+ }
+
+ attrfilter->attr_str = slapi_ch_strdup (s);
+
+ /* Now grab the filter */
+
+ filter_ptr = tmp_ptr;
+ __acl_strip_leading_space(&filter_ptr);
+ __acl_strip_trailing_space(filter_ptr);
+
+ /* trim dups the string, so we need to free it later if it's not kept. */
+ tmp_ptr = __acl_trim_filterstr(filter_ptr);
+
+ if ((f = slapi_str2filter(tmp_ptr)) == NULL) {
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+ "Bad targetattr filter for attribute %s:%s\n",
+ attrfilter->attr_str,tmp_ptr,0);
+ slapi_ch_free( (void **) &attrfilter->attr_str);
+ slapi_ch_free( (void **) &tmp_ptr);
+ return(ACL_SYNTAX_ERR);
+ }
+
+ /*
+ * Here verify that the named attribute is the only one
+ * that appears in the filter.
+ */
+
+ if (acl_verify_exactly_one_attribute( attrfilter->attr_str, f) !=
+ SLAPI_FILTER_SCAN_NOMORE) {
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+ "Exactly one attribute type per filter allowed in targattrfilters (%s)\n",
+ attrfilter->attr_str, 0);
+ slapi_ch_free( (void **) &attrfilter->attr_str);
+ slapi_ch_free( (void **) &tmp_ptr);
+ slapi_filter_free( f, 1 );
+ return(ACL_SYNTAX_ERR);
+ }
+
+ /* free the tmp_ptr */
+ slapi_ch_free( (void **) &tmp_ptr);
+ attrfilter->filterStr = slapi_ch_strdup (filter_ptr);
+ attrfilter->filter = f;
+
+ return(LDAP_SUCCESS);
+}
+
+/*
+ * Returns 0 if attr_name is the only attribute name to
+ * appear in original_filter AND it appears at least once.
+ * Otherwise returns STOP_FILTER_SCAN.
+*/
+
+static int acl_verify_exactly_one_attribute( char *attr_name,
+ Slapi_Filter *original_filter) {
+ int error_code;
+
+ return( slapi_filter_apply( original_filter, type_compare,
+ (void *)attr_name, &error_code));
+
+}
+
+static int type_compare( Slapi_Filter *f, void *arg) {
+
+ /* Compare only the base names: eg cn and cn;lang-eb will be the same. */
+
+ char *t = (char *)arg;
+ char *filter_type;
+ int rc = SLAPI_FILTER_SCAN_STOP;
+
+ if (slapi_filter_get_attribute_type( f, &filter_type) == 0) {
+ t = slapi_attr_syntax_normalize(t);
+ filter_type = slapi_attr_syntax_normalize(filter_type);
+
+ if (slapi_attr_type_cmp(filter_type, t, 1) == 0) {
+ rc = SLAPI_FILTER_SCAN_CONTINUE;
+ }
+
+ slapi_ch_free( (void **)&t );
+ slapi_ch_free( (void **)&filter_type );
+ }
+
+ return rc;
+}
diff --git a/ldap/servers/plugins/acl/aclplugin.c b/ldap/servers/plugins/acl/aclplugin.c
new file mode 100644
index 00000000..a39ef3cd
--- /dev/null
+++ b/ldap/servers/plugins/acl/aclplugin.c
@@ -0,0 +1,355 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/*
+ * There are 3 ACL PLUGIN points
+ * PREOP, POSTOP and ACL plugin
+ *
+ */
+#include "acl.h"
+#include "dirver.h"
+#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */
+
+static Slapi_PluginDesc pdesc = { "acl", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "acl access check plugin" };
+char *plugin_name = ACL_PLUGIN_NAME;
+
+/* Prototypes */
+
+static int aclplugin_preop_search ( Slapi_PBlock *pb );
+static int aclplugin_preop_modify ( Slapi_PBlock *pb );
+static int aclplugin_preop_common ( Slapi_PBlock *pb );
+
+/*******************************************************************************
+ * ACL PLUGIN Architecture
+ *
+ * There are 3 registered plugins:
+ *
+ * 1) PREOP ACL Plugin
+ * The preop plugin does all the initialization. It allocate the ACL
+ * PBlock and copies stuff from the connection if it needs to.
+ *
+ * 2) POSTOP ACL Plugin
+ * The Postop plugin cleans up the ACL PBlock. It copies Back to the
+ * connection struct. The Postop bind & Unbind cleans up the
+ * ACL CBlock ( struct hanging from conn struct ).
+ *
+ * 3) ACCESSCONTROL Plugin
+ * Main module which does the access check. There are 5 entry point
+ * from this plugin
+ * a) Initilize the ACL system i.e read all the ACLs and generate the
+ * the ACL List.
+ * b) Check for ACI syntax.
+ * c) Check for normal access.
+ * d) Check for access to a mod request.
+ * e) Update the in-memory ACL List.
+ *
+ *******************************************************************************/
+
+/*******************************************************************************
+ * PREOP
+ *******************************************************************************/
+
+/* Plugin identity is passed by the server in the plugin init function and must
+ be supplied by the plugin to all internal operations it initiates
+ */
+void* g_acl_preop_plugin_identity;
+
+int
+acl_preopInit (Slapi_PBlock *pb)
+{
+ int rc = 0;
+
+ /* save plugin identity to later pass to internal operations */
+ rc = slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &g_acl_preop_plugin_identity);
+
+ /* Declare plugin version */
+ rc = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01);
+
+ /* Provide descriptive information */
+ rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void*)&pdesc);
+
+ /* Register functions */
+ rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_SEARCH_FN, (void*)aclplugin_preop_search);
+ rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_COMPARE_FN, (void*)aclplugin_preop_search);
+ rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_ADD_FN, (void*)aclplugin_preop_modify);
+ rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_MODIFY_FN, (void*)aclplugin_preop_modify);
+ rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_MODRDN_FN, (void*)aclplugin_preop_modify);
+ rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_DELETE_FN, (void*)aclplugin_preop_modify);
+
+#if 0
+ /*
+ * XXXmcs: In order to support access control checking from
+ * extended operations, we need a SLAPI_PLUGIN_PRE_EXTENDED_FN hook.
+ * But today no such entry point exists.
+ */
+ rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_EXTENDED_FN, (void*)aclplugin_preop_modify);
+#endif
+
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, plugin_name, "<= acl_preop_Init %d\n", rc, 0, 0 );
+ return( rc );
+}
+
+/* For preop search we do two things:
+ * 1) based on the search base, we preselect the acls.
+ * 2) also get hold of a acl_pblock for use
+ */
+static int
+aclplugin_preop_search ( Slapi_PBlock *pb )
+{
+ int scope;
+ char *base = NULL;
+ int optype;
+ int isRoot;
+ int rc = 0;
+
+ TNF_PROBE_0_DEBUG(aclplugin_preop_search_start ,"ACL","");
+
+ slapi_pblock_get ( pb, SLAPI_OPERATION_TYPE, &optype );
+ slapi_pblock_get ( pb, SLAPI_REQUESTOR_ISROOT, &isRoot );
+
+ if ( isRoot ) {
+ TNF_PROBE_1_DEBUG(aclplugin_preop_search_end ,"ACL","",
+ tnf_string,isroot,"");
+ return rc;
+ }
+
+ slapi_pblock_get( pb, SLAPI_SEARCH_TARGET, &base );
+ /* For anonymous client doing search nothing needs to be set up */
+ if ( optype == SLAPI_OPERATION_SEARCH && aclanom_is_client_anonymous ( pb ) &&
+ ! slapi_dn_issuffix( base, "cn=monitor") ) {
+ TNF_PROBE_1_DEBUG(aclplugin_preop_search_end ,"ACL","",
+ tnf_string,anon,"");
+ return rc;
+ }
+
+ if ( 0 == ( rc = aclplugin_preop_common( pb ))) {
+ slapi_pblock_get( pb, SLAPI_SEARCH_SCOPE, &scope );
+ acllist_init_scan ( pb, scope, base );
+ }
+
+ TNF_PROBE_0_DEBUG(aclplugin_preop_search_end ,"ACL","");
+
+ return rc;
+}
+
+/*
+ * For rest of the opertion type, we get a hold of the acl
+ * private Block.
+ */
+static int
+aclplugin_preop_modify ( Slapi_PBlock *pb )
+{
+ /*
+ * Note: since we don't keep the anom profile for modifies, we have to go
+ * through the regular process to check the access.
+ */
+ return aclplugin_preop_common( pb );
+}
+
+/*
+ * Common function that is called by aclplugin_preop_search() and
+ * aclplugin_preop_modify().
+ *
+ * Return values:
+ * 0 - all is well; proceed.
+ * 1 - fatal error; result has been sent to client.
+ */
+static int
+aclplugin_preop_common( Slapi_PBlock *pb )
+{
+ char *proxy_dn; /* id being assumed */
+ char *dn; /* proxy master */
+ char *errtext = NULL;
+ int lderr;
+ Acl_PBlock *aclpb;
+
+ TNF_PROBE_0_DEBUG(aclplugin_preop_common_start ,"ACL","");
+
+ aclpb = acl_get_aclpb ( pb, ACLPB_BINDDN_PBLOCK );
+
+ /*
+ * The following mallocs memory for proxy_dn, but not the dn.
+ * The proxy_dn is the id being assumed, while dn
+ * is the "proxy master".
+ */
+ proxy_dn = NULL;
+ if ( LDAP_SUCCESS != ( lderr = acl_get_proxyauth_dn( pb, &proxy_dn,
+ &errtext ))) {
+ /*
+ * Fatal error -- send a result to the client and arrange to skip
+ * any further processing.
+ */
+ slapi_send_ldap_result( pb, lderr, NULL, errtext, 0, NULL );
+ TNF_PROBE_1_DEBUG(aclplugin_preop_common_end ,"ACL","",
+ tnf_string,proxid_error,"");
+
+ return 1; /* skip any further processing */
+ }
+ slapi_pblock_get ( pb, SLAPI_REQUESTOR_DN, &dn );
+
+
+ /*
+ * The dn is copied into the aclpb during initialization.
+ */
+ if ( proxy_dn) {
+ TNF_PROBE_0_DEBUG(proxyacpb_init_start,"ACL","");
+
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name,
+ "proxied authorization dn is (%s)\n", proxy_dn );
+ acl_init_aclpb ( pb, aclpb, proxy_dn, 1 );
+ aclpb = acl_new_proxy_aclpb (pb );
+ acl_init_aclpb ( pb, aclpb, dn, 0 );
+ slapi_ch_free ( (void **) &proxy_dn );
+
+ TNF_PROBE_0_DEBUG(proxyacpb_init_end,"ACL","");
+
+ } else {
+ TNF_PROBE_0_DEBUG(aclpb_init_start,"ACL","");
+ acl_init_aclpb ( pb, aclpb, dn, 1 );
+ TNF_PROBE_0_DEBUG(aclpb_init_end,"ACL","");
+
+ }
+
+ TNF_PROBE_0_DEBUG(aclplugin_preop_common_end ,"ACL","");
+
+ return 0;
+}
+
+/*******************************************************************************
+ * POSTOP
+ *******************************************************************************/
+
+/*******************************************************************************
+ * ACCESSCONTROL PLUGIN
+ *******************************************************************************/
+
+void* g_acl_plugin_identity;
+
+/* For now, the acl component is implemented as 2 different plugins */
+/* Return the right plugin identity */
+void * aclplugin_get_identity(int plug) {
+ if (plug == ACL_PLUGIN_IDENTITY)
+ return g_acl_plugin_identity;
+ if (plug == ACL_PREOP_PLUGIN_IDENTITY)
+ return g_acl_preop_plugin_identity;
+ return NULL;
+}
+
+int
+aclplugin_init (Slapi_PBlock *pb )
+{
+
+ int rc = 0; /* OK */
+ rc = aclinit_main ( pb );
+
+ return rc;
+
+}
+int
+aclplugin_stop ( Slapi_PBlock *pb )
+{
+ int rc = 0; /* OK */
+
+ /* nothing to be done now */
+ return rc;
+}
+
+int
+acl_init( Slapi_PBlock *pb )
+{
+ int rc =0;
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, plugin_name, "=> acl_init\n", 0, 0, 0 );
+
+ if ( 0 != acl_init_ext() ) {
+ slapi_log_error ( SLAPI_LOG_FATAL, plugin_name,
+ "Unable to initialize the extensions\n");
+ return 1;
+ }
+
+ /* save plugin identity to later pass to internal operations */
+ rc = slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &g_acl_plugin_identity);
+
+ rc = slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION,
+ (void *) SLAPI_PLUGIN_VERSION_01 );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION,
+ (void *)&pdesc );
+
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_START_FN, (void *) aclplugin_init );
+ rc = slapi_pblock_set( pb, SLAPI_PLUGIN_CLOSE_FN, (void *) aclplugin_stop );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_ACL_SYNTAX_CHECK,
+ (void *) acl_verify_aci_syntax );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_ACL_ALLOW_ACCESS,
+ (void *) acl_access_allowed_main );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_ACL_MODS_ALLOWED,
+ (void *) acl_check_mods );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_ACL_MODS_UPDATE,
+ (void *) acl_modified );
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, plugin_name, "<= acl_init %d\n", rc, 0, 0 );
+ return( rc );
+}
+
+/*
+ *
+ * acl_access_allowed_main
+ * Main interface to the plugin. Calls different access check functions
+ * based on the flag.
+ *
+ *
+ * Returns:
+ * LDAP_SUCCESS -- access is granted
+ * LDAP_INSUFFICIENT_ACCESS -- access denied
+ * <other ldap error> -- ex: opererations error
+ *
+ */
+int
+acl_access_allowed_main ( Slapi_PBlock *pb, Slapi_Entry *e, char **attrs,
+ struct berval *val, int access , int flags, char **errbuf)
+{
+ int rc =0;
+ char *attr = NULL;
+
+ TNF_PROBE_0_DEBUG(acl_access_allowed_main_start,"ACL","");
+
+ if (attrs && *attrs) attr = attrs[0];
+
+ if (ACLPLUGIN_ACCESS_READ_ON_ENTRY == flags)
+ rc = acl_read_access_allowed_on_entry ( pb, e, attrs, access);
+ else if ( ACLPLUGIN_ACCESS_READ_ON_ATTR == flags)
+ rc = acl_read_access_allowed_on_attr ( pb, e, attr, val, access);
+ else if ( ACLPLUGIN_ACCESS_READ_ON_VLV == flags)
+ rc = acl_access_allowed_disjoint_resource ( pb, e, attr, val, access);
+ else if ( ACLPLUGIN_ACCESS_MODRDN == flags)
+ rc = acl_access_allowed_modrdn ( pb, e, attr, val, access);
+ else if ( ACLPLUGIN_ACCESS_GET_EFFECTIVE_RIGHTS == flags)
+ rc = acl_get_effective_rights ( pb, e, attrs, val, access, errbuf );
+ else
+ rc = acl_access_allowed ( pb, e, attr, val, access);
+
+ /* generate the appropriate error message */
+ if ( ( rc != LDAP_SUCCESS ) && errbuf &&
+ ( ACLPLUGIN_ACCESS_GET_EFFECTIVE_RIGHTS != flags ) &&
+ ( access & ( SLAPI_ACL_WRITE | SLAPI_ACL_ADD | SLAPI_ACL_DELETE ))) {
+
+ char *edn = slapi_entry_get_dn ( e );
+
+ acl_gen_err_msg(access, edn, attr, errbuf);
+ }
+
+ TNF_PROBE_0_DEBUG(acl_access_allowed_main_end,"ACL","");
+
+ return rc;
+}
+#ifdef _WIN32
+
+int *module_ldap_debug = 0;
+void plugin_init_debug_level ( int *level_ptr )
+{
+ module_ldap_debug = level_ptr;
+}
+#endif
+
diff --git a/ldap/servers/plugins/acl/aclproxy.c b/ldap/servers/plugins/acl/aclproxy.c
new file mode 100644
index 00000000..9065bddc
--- /dev/null
+++ b/ldap/servers/plugins/acl/aclproxy.c
@@ -0,0 +1,195 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include "acl.h"
+
+#define BEGIN do {
+#define END } while(0);
+
+/* ------------------------------------------------------------
+ * LDAPProxyAuth
+ *
+ * ProxyAuthControl ::= SEQUENCE {
+ * authorizationDN LDAPDN
+ * }
+ */
+struct LDAPProxyAuth
+{
+ char *auth_dn;
+};
+typedef struct LDAPProxyAuth LDAPProxyAuth;
+
+/*
+ * delete_LDAPProxyAuth
+ */
+static void
+delete_LDAPProxyAuth(LDAPProxyAuth *spec)
+{
+ if (!spec) return;
+
+ slapi_ch_free((void**)&spec->auth_dn);
+ slapi_ch_free((void**)&spec);
+}
+
+/*
+ * parse_LDAPProxyAuth
+ *
+ * Parse a BER encoded value into the compoents of the LDAP ProxyAuth control.
+ * The 'version' parameter should be 1 or 2.
+ *
+ * Returns an LDAP error code (LDAP_SUCCESS if all goes well) and sets
+ * *errtextp if appropriate.
+ */
+static int
+parse_LDAPProxyAuth(struct berval *spec_ber, int version, char **errtextp,
+ LDAPProxyAuth **out)
+{
+ int lderr = LDAP_OPERATIONS_ERROR; /* pessimistic */
+ LDAPProxyAuth *spec = NULL;
+ BerElement *ber = NULL;
+ char *errstring = "unable to parse proxied authorization control";
+
+
+ BEGIN
+ unsigned long tag;
+
+ if ( version != 1 && version != 2 ) {
+ break;
+ }
+
+ /* create_LDAPProxyAuth */
+ spec = (LDAPProxyAuth*)slapi_ch_calloc(1,sizeof (LDAPProxyAuth));
+ if (!spec) {
+ break;
+ }
+
+ ber = ber_init(spec_ber);
+ if (!ber) {
+ break;
+ }
+
+ if ( version == 1 ) {
+ tag = ber_scanf(ber, "{a}", &spec->auth_dn);
+ } else {
+ tag = ber_scanf(ber, "a", &spec->auth_dn);
+ }
+ if (tag == LBER_ERROR) {
+ lderr = LDAP_PROTOCOL_ERROR;
+ break;
+ }
+
+ /*
+ * In version 2 of the control, the control value is actually an
+ * authorization ID (see section 9 of RFC 2829). We only support
+ * the "dnAuthzId" flavor, which looks like "dn:<DN>" where <DN> is
+ * an actual DN, e.g., "dn:uid=bjensen,dc=example,dc=com". So we
+ * need to strip off the dn: if present and reject the operation if
+ * not.
+ */
+ if (2 == version) {
+ if ( NULL == spec->auth_dn || strlen( spec->auth_dn ) < 3 ||
+ strncmp( "dn:", spec->auth_dn, 3 ) != 0 ) {
+ lderr = LDAP_INSUFFICIENT_ACCESS; /* per Proxied Auth. I-D */
+ errstring = "proxied authorization id must be a DN (dn:...)";
+ break;
+ }
+ strcpy( spec->auth_dn, spec->auth_dn + 3 );
+ }
+
+ slapi_dn_normalize(spec->auth_dn);
+ lderr = LDAP_SUCCESS; /* got it! */
+ END
+
+ /* Cleanup */
+ if (ber) ber_free(ber, 0);
+
+ if ( LDAP_SUCCESS != lderr)
+ {
+ if (spec) delete_LDAPProxyAuth(spec);
+ spec = 0;
+ if ( NULL != errtextp ) {
+ *errtextp = errstring;
+ }
+ }
+
+ *out = spec;
+
+ return lderr;
+}
+
+/*
+ * proxyauth_dn - find the users DN in the proxyauth control if it is
+ * present. The return value has been malloced for you.
+ *
+ * Returns an LDAP error code. If anything than LDAP_SUCCESS is returned,
+ * the error should be returned to the client. LDAP_SUCCESS is always
+ * returned if the proxy auth control is not present or not critical.
+ */
+int
+acl_get_proxyauth_dn( Slapi_PBlock *pb, char **proxydnp, char **errtextp )
+{
+ char *dn = 0;
+ LDAPProxyAuth *spec = 0;
+ int rv, lderr = LDAP_SUCCESS; /* optimistic */
+
+ BEGIN
+ struct berval *spec_ber;
+ LDAPControl **controls;
+ int present;
+ int critical;
+ int version = 1;
+
+ rv = slapi_pblock_get( pb, SLAPI_REQCONTROLS, &controls );
+ if (rv) break;
+
+ present = slapi_control_present( controls, LDAP_CONTROL_PROXYAUTH,
+ &spec_ber, &critical );
+ if (!present) {
+ present = slapi_control_present( controls, LDAP_CONTROL_PROXIEDAUTH,
+ &spec_ber, &critical );
+ if (!present) break;
+ version = 2;
+ /*
+ * Note the according to the Proxied Authorization I-D, the
+ * control is always supposed to be marked critical by the
+ * client. If it is not, we return a protocolError.
+ */
+ if ( !critical ) {
+ lderr = LDAP_PROTOCOL_ERROR;
+ if ( NULL != errtextp ) {
+ *errtextp = "proxy control must be marked critical";
+ }
+ break;
+ }
+ }
+
+ rv = parse_LDAPProxyAuth(spec_ber, version, errtextp, &spec);
+ if (LDAP_SUCCESS != rv) {
+ if ( critical ) {
+ lderr = rv;
+ }
+ break;
+ }
+
+ dn = slapi_ch_strdup(spec->auth_dn);
+ if (slapi_dn_isroot(dn) ) {
+ lderr = LDAP_UNWILLING_TO_PERFORM;
+ *errtextp = "Proxy dn should not be rootdn";
+ break;
+
+ }
+ END
+
+ if (spec) delete_LDAPProxyAuth(spec);
+
+ if ( NULL != proxydnp ) {
+ *proxydnp = dn;
+ } else {
+ slapi_ch_free( (void **)&dn );
+ }
+
+ return lderr;
+}
+
diff --git a/ldap/servers/plugins/acl/aclutil.c b/ldap/servers/plugins/acl/aclutil.c
new file mode 100644
index 00000000..168ca482
--- /dev/null
+++ b/ldap/servers/plugins/acl/aclutil.c
@@ -0,0 +1,1475 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include "acl.h"
+
+/**************************************************************************
+* Defines and usefuls stuff
+****************************************************************************/
+
+/*************************************************************************
+* Prototypes
+*************************************************************************/
+static void aclutil__typestr (int type , char str[]);
+static void aclutil__Ruletypestr (int type , char str[]);
+static char* __aclutil_extract_dn_component ( char **e_dns, int position,
+ char *attrName );
+static char* acl_get_final_component(char *macro_prefix) ;
+static char* acl_match_component( char *start, char *component);
+static int aclutil_compare_components( char * comp1, char *comp2);
+static int acl_find_comp_start(char * s, int pos );
+static PRIntn acl_ht_free_entry_and_value(PLHashEntry *he, PRIntn i,
+ void *arg);
+static PLHashNumber acl_ht_hash( const void *key);
+static PRIntn acl_ht_display_entry(PLHashEntry *he, PRIntn i, void *arg);
+
+/***************************************************************************/
+/* UTILITY FUNCTIONS */
+/***************************************************************************/
+int
+aclutil_str_appened(char **str1, const char *str2)
+{
+ int new_len;
+
+ if ( str1 == NULL || str2 == NULL )
+ return(0);
+
+ if ( *str1 == NULL ) {
+ new_len = strlen(str2) + 1;
+ *str1 = (char *)slapi_ch_malloc(new_len);
+ *str1[0] = 0;
+ } else {
+ new_len = strlen(*str1) + strlen(str2) + 1;
+ *str1 = (char *)slapi_ch_realloc(*str1, new_len);
+ }
+ if ( *str1 == NULL )
+ return(-1);
+
+ strcat(*str1, str2);
+ return(0);
+}
+
+/***************************************************************************/
+/* Print routines */
+/***************************************************************************/
+
+/* print eroror message returned from the ACL Library */
+#define ACLUTIL_ACLLIB_MSGBUF_LEN 200
+void
+acl_print_acllib_err (NSErr_t *errp , char * str)
+{
+ char msgbuf[ ACLUTIL_ACLLIB_MSGBUF_LEN ];
+
+ if ((NULL == errp ) || !slapi_is_loglevel_set ( SLAPI_LOG_ACL ) )
+ return;
+
+ aclErrorFmt(errp, msgbuf, ACLUTIL_ACLLIB_MSGBUF_LEN, 1);
+ msgbuf[ACLUTIL_ACLLIB_MSGBUF_LEN-1] = '\0';
+
+ if (msgbuf)
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name,"ACL LIB ERR:(%s)(%s)\n",
+ msgbuf, str ? str: "NULL",0);
+}
+void
+aclutil_print_aci (aci_t *aci_item, char *type)
+{
+ char str[BUFSIZ];
+ const char *dn;
+
+ if ( ! slapi_is_loglevel_set ( SLAPI_LOG_ACL ) )
+ return;
+
+ if (!aci_item) {
+
+ slapi_log_error (SLAPI_LOG_ACL, plugin_name,
+ "acl__print_aci: Null item\n",0,0,0);
+ return;
+ }
+
+
+ slapi_log_error (SLAPI_LOG_ACL, plugin_name,
+ "***BEGIN ACL INFO[ Name:%s]***\n", aci_item->aclName);
+
+ slapi_log_error (SLAPI_LOG_ACL, plugin_name,
+ "ACL Index:%d ACL_ELEVEL:%d\n", aci_item->aci_index, aci_item->aci_elevel );
+ aclutil__access_str (aci_item->aci_access, str);
+ aclutil__typestr (aci_item->aci_type, &str[strlen(str)]);
+ slapi_log_error (SLAPI_LOG_ACL, plugin_name,
+ "ACI type:(%s)\n", str);
+
+ aclutil__Ruletypestr (aci_item->aci_ruleType, str);
+ slapi_log_error (SLAPI_LOG_ACL, plugin_name,
+ "ACI RULE type:(%s)\n",str);
+
+ dn = slapi_sdn_get_dn ( aci_item->aci_sdn );
+ slapi_log_error (SLAPI_LOG_ACL, plugin_name,
+ "Slapi_Entry DN:%s\n", escape_string_with_punctuation (dn, str));
+
+ slapi_log_error (SLAPI_LOG_ACL, plugin_name,
+ "***END ACL INFO*****************************\n");
+
+}
+void
+aclutil_print_err (int rv , const Slapi_DN *sdn, const struct berval* val,
+ char **errbuf)
+{
+ char ebuf [BUFSIZ];
+ /*
+ * The maximum size of line is ebuf_size + the log message
+ * itself (less than 200 characters for all but potentially ACL_INVALID_TARGET)
+ */
+ char line [BUFSIZ + 200];
+ char str [1024];
+ const char *dn;
+ char *lineptr = line;
+ char *newline = NULL;
+
+ if ( rv >= 0)
+ return;
+
+ if (val->bv_len > 0 && val->bv_val != NULL) {
+ sprintf (str, "%.1023s", val->bv_val);
+ } else {
+ str[0] = '\0';
+ }
+
+ dn = slapi_sdn_get_dn ( sdn );
+ if (dn && (rv == ACL_INVALID_TARGET) && ((strlen(dn) + strlen(str)) > BUFSIZ)) {
+ /*
+ * if (str_length + dn_length + 200 char message) > (BUFSIZ + 200) line
+ * we have to make space for a bigger line...
+ */
+ newline = slapi_ch_malloc(strlen(dn) + strlen(str) + 200);
+ lineptr = newline;
+ }
+
+ switch (rv) {
+ case ACL_TARGET_FILTER_ERR:
+ sprintf (line, "ACL Internal Error(%d): "
+ "Error in generating the target filter for the ACL(%s)\n",
+ rv, escape_string_with_punctuation (str, ebuf));
+ break;
+ case ACL_TARGETATTR_FILTER_ERR:
+ sprintf (line, "ACL Internal Error(%d): "
+ "Error in generating the targetattr filter for the ACL(%s)\n",
+ rv, escape_string_with_punctuation (str, ebuf));
+ break;
+ case ACL_TARGETFILTER_ERR:
+ sprintf (line, "ACL Internal Error(%d): "
+ "Error in generating the targetfilter filter for the ACL(%s)\n",
+ rv, escape_string_with_punctuation (str, ebuf));
+ break;
+ case ACL_SYNTAX_ERR:
+ sprintf (line, "ACL Syntax Error(%d):%s\n",
+ rv, escape_string_with_punctuation (str, ebuf));
+ break;
+ case ACL_ONEACL_TEXT_ERR:
+ sprintf (line, "ACL Syntax Error in the Bind Rules(%d):%s\n",
+ rv, escape_string_with_punctuation (str, ebuf));
+ break;
+ case ACL_ERR_CONCAT_HANDLES:
+ sprintf (line, "ACL Internal Error(%d): "
+ "Error in Concatenating List handles\n",
+ rv);
+ break;
+ case ACL_INVALID_TARGET:
+ sprintf (lineptr, "ACL Invalid Target Error(%d): "
+ "Target is beyond the scope of the ACL(SCOPE:%s)",
+ rv, dn ? escape_string_with_punctuation (dn, ebuf) : "NULL");
+ sprintf (lineptr + strlen(lineptr), " %s\n", escape_string_with_punctuation (str, ebuf));
+ break;
+ case ACL_INVALID_AUTHMETHOD:
+ sprintf (line, "ACL Multiple auth method Error(%d):"
+ "Multiple Authentication Metod in the ACL(%s)\n",
+ rv, escape_string_with_punctuation (str, ebuf));
+ break;
+ case ACL_INVALID_AUTHORIZATION:
+ sprintf (line, "ACL Syntax Error(%d):"
+ "Invalid Authorization statement in the ACL(%s)\n",
+ rv, escape_string_with_punctuation (str, ebuf));
+ break;
+ case ACL_INCORRECT_ACI_VERSION:
+ sprintf (line, "ACL Syntax Error(%d):"
+ "Incorrect version Number in the ACL(%s)\n",
+ rv, escape_string_with_punctuation (str, ebuf));
+ break;
+ default:
+ sprintf (line, "ACL Internal Error(%d):"
+ "ACL generic error (%s)\n",
+ rv, escape_string_with_punctuation (str, ebuf));
+ break;
+ }
+
+ if (errbuf) {
+ /* If a buffer is provided, then copy the error */
+ aclutil_str_appened(errbuf, lineptr );
+ }
+
+ slapi_log_error( SLAPI_LOG_FATAL, plugin_name, "%s", lineptr);
+ if (newline) slapi_ch_free((void **) &newline);
+}
+
+/***************************************************************************
+* Convert access to str
+***************************************************************************/
+char*
+aclutil__access_str (int type , char str[])
+{
+ char *p;
+
+ str[0] = '\0';
+ p = str;
+
+ if (type & SLAPI_ACL_COMPARE) {
+ strcpy (p, "compare ");
+ p = strchr (p, '\0');
+ }
+ if (type & SLAPI_ACL_SEARCH) {
+ strcpy (p, "search ");
+ p = strchr (p, '\0');
+ }
+ if (type & SLAPI_ACL_READ) {
+ strcpy (p, "read ");
+ p = strchr (p, '\0');
+ }
+ if (type & SLAPI_ACL_WRITE) {
+ strcpy (p, "write ");
+ p = strchr (p, '\0');
+ }
+ if (type & SLAPI_ACL_DELETE) {
+ strcpy (p, "delete ");
+ p = strchr (p, '\0');
+ }
+ if (type & SLAPI_ACL_ADD) {
+ strcpy (p, "add ");
+ p = strchr (p, '\0');
+ }
+ if (type & SLAPI_ACL_SELF) {
+ strcpy (p, "self ");
+ p = strchr (p, '\0');
+ }
+ if (type & SLAPI_ACL_PROXY) {
+ strcpy (p, "proxy ");
+ }
+ return str;
+}
+
+/***************************************************************************
+* Convert type to str
+***************************************************************************/
+static void
+aclutil__typestr (int type , char str[])
+{
+ char *p;
+
+ /* Start copying in at whatever location is passed in */
+
+ p = str;
+
+ if (type & ACI_TARGET_DN) {
+ strcpy (p, "target_DN ");
+ p = strchr (p, '\0');
+ }
+ if (type & ACI_TARGET_ATTR) {
+ strcpy (p, "target_attr ");
+ p = strchr (p, '\0');
+ }
+ if (type & ACI_TARGET_PATTERN) {
+ strcpy (p, "target_patt ");
+ p = strchr (p, '\0');
+ }
+ if ((type & ACI_TARGET_ATTR_ADD_FILTERS) | (type & ACI_TARGET_ATTR_DEL_FILTERS)) {
+ strcpy (p, "targetattrfilters ");
+ p = strchr (p, '\0');
+ }
+ if (type & ACI_TARGET_FILTER) {
+ strcpy (p, "target_filter ");
+ p = strchr (p, '\0');
+ }
+ if (type & ACI_ACLTXT) {
+ strcpy (p, "acltxt ");
+ p = strchr (p, '\0');
+ }
+ if (type & ACI_TARGET_NOT) {
+ strcpy (p, "target_not ");
+ p = strchr (p, '\0');
+ }
+ if (type & ACI_TARGET_ATTR_NOT) {
+ strcpy (p, "target_attr_not ");
+ p = strchr (p, '\0');
+ }
+ if (type & ACI_TARGET_FILTER_NOT) {
+ strcpy (p, "target_filter_not ");
+ p = strchr (p, '\0');
+ }
+
+ if (type & ACI_HAS_ALLOW_RULE) {
+ strcpy (p, "allow_rule ");
+ p = strchr (p, '\0');
+ }
+ if (type & ACI_HAS_DENY_RULE) {
+ strcpy (p, "deny_rule ");
+ p = strchr (p, '\0');
+ }
+}
+static void
+aclutil__Ruletypestr (int type , char str[])
+{
+ char *p;
+
+ str[0] = '\0';
+ p = str;
+ if ( type & ACI_USERDN_RULE) {
+ strcpy (p, "userdn ");
+ p = strchr (p, '\0');
+ }
+ if ( type & ACI_USERDNATTR_RULE) {
+ strcpy (p, "userdnattr ");
+ p = strchr (p, '\0');
+ }
+ if ( type & ACI_USERATTR_RULE) {
+ strcpy (p, "userattr ");
+ p = strchr (p, '\0');
+ }
+ if ( type & ACI_GROUPDN_RULE) {
+ strcpy (p, "groupdn ");
+ p = strchr (p, '\0');
+ }
+ if ( type & ACI_GROUPDNATTR_RULE) {
+ strcpy (p, "groupdnattr ");
+ p = strchr (p, '\0');
+ }
+ if ( type & ACI_ROLEDN_RULE) {
+ strcpy (p, "roledn ");
+ p = strchr (p, '\0');
+ }
+ if ( type & ACI_IP_RULE) {
+ strcpy (p, "ip ");
+ p = strchr (p, '\0');
+ }
+ if ( type & ACI_DNS_RULE) {
+ strcpy (p, "dns ");
+ p = strchr (p, '\0');
+ }
+ if ( type & ACI_TIMEOFDAY_RULE) {
+ strcpy (p, "timeofday ");
+ p = strchr (p, '\0');
+ }
+ if ( type & ACI_DAYOFWEEK_RULE) {
+ strcpy (p, "dayofweek ");
+ p = strchr (p, '\0');
+ }
+ if ( type & ACI_AUTHMETHOD_RULE) {
+ strcpy (p, "authmethod ");
+ p = strchr (p, '\0');
+ }
+ if ( type & ACI_PARAM_DNRULE) {
+ strcpy (p, "paramdn ");
+ p = strchr (p, '\0');
+ }
+ if ( type & ACI_PARAM_ATTRRULE) {
+ strcpy (p, "paramAttr ");
+ p = strchr (p, '\0');
+ }
+}
+/*
+** acl_gen_err_msg
+** This function is called by backend to generate the error message
+** if access is denied.
+*/
+void
+acl_gen_err_msg(int access, char *edn, char *attr, char **errbuf)
+{
+ char *line = NULL;
+
+ if (access & SLAPI_ACL_WRITE) {
+ line = PR_smprintf(
+ "Insufficient 'write' privilege to the '%s' attribute of entry '%s'.\n",
+ attr ? attr: "NULL", edn);
+ } else if ( access & SLAPI_ACL_ADD ) {
+ line = PR_smprintf(
+ "Insufficient 'add' privilege to add the entry '%s'.\n",edn);
+
+ } else if ( access & SLAPI_ACL_DELETE ) {
+ line = PR_smprintf(
+ "Insufficient 'delete' privilege to delete the entry '%s'.\n",edn);
+ }
+ aclutil_str_appened(errbuf, line );
+
+ if (line) {
+ PR_smprintf_free(line);
+ line = NULL;
+ }
+}
+short
+aclutil_gen_signature ( short c_signature )
+{
+ short o_signature;
+ o_signature = c_signature ^ (slapi_rand() % 32768);
+ if (!o_signature)
+ o_signature = c_signature ^ (slapi_rand() % 32768);
+
+ return o_signature;
+}
+
+void
+aclutil_print_resource( struct acl_pblock *aclpb, char *right , char *attr, char *clientdn )
+{
+
+ char str[BUFSIZ];
+ const char *dn;
+
+
+ if ( aclpb == NULL) return;
+
+ if ( ! slapi_is_loglevel_set ( SLAPI_LOG_ACL ) )
+ return;
+
+ slapi_log_error (SLAPI_LOG_ACL, plugin_name, " ************ RESOURCE INFO STARTS *********\n",0,0,0);
+ slapi_log_error (SLAPI_LOG_ACL, plugin_name, " Client DN: %s\n",
+ clientdn ? escape_string_with_punctuation (clientdn, str) : "NULL", 0,0);
+ aclutil__access_str (aclpb->aclpb_access, str);
+ aclutil__typestr (aclpb->aclpb_res_type, &str[strlen(str)]);
+ slapi_log_error (SLAPI_LOG_ACL, plugin_name, " resource type:%d(%s)\n",
+ aclpb->aclpb_res_type, str, 0);
+
+ dn = slapi_sdn_get_dn ( aclpb->aclpb_curr_entry_sdn );
+ slapi_log_error (SLAPI_LOG_ACL, plugin_name, " Slapi_Entry DN: %s\n",
+ dn ? escape_string_with_punctuation ( dn , str) : "NULL",0,0);
+
+ slapi_log_error (SLAPI_LOG_ACL, plugin_name, " ATTR: %s\n", attr ? attr : "NULL",0,0);
+ slapi_log_error (SLAPI_LOG_ACL, plugin_name, " rights:%s\n", right ? right: "NULL",0,0);
+ slapi_log_error (SLAPI_LOG_ACL, plugin_name, " ************ RESOURCE INFO ENDS *********\n",0,0,0);
+}
+/*
+ * The input string contains a rule like
+ * "cn=helpdesk, ou=$attr.deptName, o=$dn.o, o=ISP"
+ *
+ * Where $attr -- means look into the attribute list for values
+ * $dn -- means look into the entry's dn
+ *
+ * We extract the values from the entry and returned a string
+ * with the values added.
+ * For "$attr" rule - if we find multiple values then it is
+ * the pattern is not expanded.
+ * For "$dn" rule, if we find multiple of them, we use the relative
+ * position.
+ * NOTE: The caller is responsible in freeing the memory.
+ */
+char *
+aclutil_expand_paramString ( char *str, Slapi_Entry *e )
+{
+
+ char **e_dns;
+ char **a_dns;
+ char *attrName;
+ char *s, *p;
+ char *attrVal;
+ int i, len;
+ int ncomponents, type;
+ int rc = -1;
+ char *buf = NULL;
+
+
+ e_dns = ldap_explode_dn ( slapi_entry_get_ndn ( e ), 0 );
+ a_dns = ldap_explode_dn ( str, 0 );
+
+ i = 0;
+ ncomponents = 0;
+ while ( a_dns[ncomponents] )
+ ncomponents++;
+
+
+ for (i=0; i < ncomponents; i++ ) {
+
+ /* Look for"$" char */
+ if ( (s = strchr ( a_dns[i], '$') ) != NULL) {
+ p = s;
+ s++;
+ if ( strncasecmp (s, "dn", 2) == 0 )
+ type = 1;
+ else if ( strncasecmp (s, "attr", 4) == 0 )
+ type = 2;
+ else {
+ /* error */
+ goto cleanup;
+ }
+ *p = '\0';
+ aclutil_str_appened ( &buf,a_dns[i]);
+
+ if ( type == 1 ) {
+ /* xyz = $dn.o */
+ s +=3;
+ attrName = s;
+
+ attrVal = __aclutil_extract_dn_component (e_dns,
+ ncomponents-i, attrName);
+ if ( NULL == attrVal ) /*error*/
+ goto cleanup;
+
+ } else {
+ Slapi_Attr *attr;
+ const struct berval *attrValue;
+ int kk;
+ Slapi_Value *sval, *t_sval;
+
+
+ /* The pattern is x=$attr.o" */
+ s +=5;
+ attrName = s;
+
+ slapi_entry_attr_find ( e, attrName, &attr );
+ if ( NULL == attr )
+ goto cleanup;
+
+ kk= slapi_attr_first_value ( attr, &sval );
+ if ( kk != -1 ) {
+ t_sval = sval;
+ kk= slapi_attr_next_value( attr, kk, &sval );
+ if ( kk != -1 ) /* can't handle multiple --error */
+ goto cleanup;
+ }
+ attrValue = slapi_value_get_berval ( t_sval );
+ attrVal = attrValue->bv_val;
+ }
+ } else {
+ attrVal = a_dns[i];
+ }
+ aclutil_str_appened ( &buf, attrVal);
+ aclutil_str_appened ( &buf, ",");
+ }
+ rc = 0; /* everything is okay*/
+ /* remove the last comma */
+ len = strlen ( buf);
+ buf[len-1] = '\0';
+
+cleanup:
+
+ ldap_value_free ( a_dns );
+ ldap_value_free ( e_dns );
+ if ( 0 != rc ) /* error */ {
+ slapi_ch_free ( (void **) &buf );
+ buf = NULL;
+ }
+
+ return buf;
+}
+static char *
+__aclutil_extract_dn_component ( char **e_dns, int position, char *attrName )
+{
+
+ int i, matched, len;
+ char *s;
+ int matchedPosition;
+
+ len = strlen ( attrName );
+
+ /* First check if there thare are multiple of these */
+ i = matched = 0;
+ while ( e_dns[i] ) {
+ if (0 == strncasecmp (e_dns[i], attrName, len) ) {
+ matched++;
+ matchedPosition = i;
+ }
+ i++;
+ }
+
+ if (!matched )
+ return NULL;
+
+ if ( matched > 1 ) {
+ matchedPosition = i - position;
+ }
+
+ if ( NULL == e_dns[matchedPosition])
+ return NULL;
+
+ s = strstr ( e_dns[matchedPosition], "=");
+ if ( NULL == s)
+ return NULL;
+ else
+ return s+1;
+}
+
+/*
+ * Does the first component of ndn match the first component of match_this ?
+*/
+
+int
+acl_dn_component_match( const char *ndn, char *match_this, int component_number) {
+
+ return(1);
+}
+
+/*
+ * Here, ndn is a resource dn and match_this is a dn, containing a macro, ($dn).
+ *
+ * eg. ndn is cn=fred,ou=groups,ou=people,ou=icnc,o=ISP and
+ * match_this is "ou=Groups,($dn),o=ISP" or
+ * "cn=*,ou=Groups,($dn),o=ISP".
+ *
+ * They match if:
+ * match_this is a suffix of ndn
+ *
+ * It returns NULL, if they do not match.
+ * Otherwise it returns a copy of the substring of ndn that matches the ($dn).
+ *
+ * eg. in the above example, "ou=people,ou=icnc"
+*/
+
+char *
+acl_match_macro_in_target( const char *ndn, char * match_this,
+ char *macro_ptr) {
+
+ char *macro_prefix = NULL;
+ int macro_prefix_len = 0;
+ char *macro_suffix = NULL;
+ char *tmp_ptr = NULL;
+ char *matched_val = NULL;
+ char *ndn_suffix_start = NULL;
+ char *macro_prefix_final_component = NULL;
+ char *ret_val = NULL;
+ int ndn_len = 0;
+ int macro_suffix_len = 0;
+ int ndn_prefix_len = 0;
+ int ndn_prefix_end = 0;
+ int matched_val_len = 0;
+
+ /*
+ * First, grab the macro_suffix--the bit after the ($dn)
+ *
+ */
+
+ if (strlen(macro_ptr) == strlen(ACL_TARGET_MACRO_DN_KEY)) {
+ macro_suffix = NULL; /* just ($dn) */
+ } else {
+ if ( macro_ptr[strlen(ACL_TARGET_MACRO_DN_KEY)] == ',') {
+ macro_suffix = &macro_ptr[strlen(ACL_TARGET_MACRO_DN_KEY) + 1];
+ } else {
+ macro_suffix = &macro_ptr[strlen(ACL_TARGET_MACRO_DN_KEY)];
+ }
+ }
+
+ /*
+ * First ensure that the suffix of match_this is
+ * a suffix of ndn.
+ */
+
+ ndn_len = strlen(ndn);
+ if ( macro_suffix != NULL) {
+ macro_suffix_len = strlen(macro_suffix);
+ if( macro_suffix_len >= ndn_len ) {
+
+ /*
+ * eg ndn: o=icnc,o=sun.com
+ * match_this: ($dn),o=icnc,o=sun.com
+ */
+ return(NULL); /* ($dn) must match something. */
+ } else {
+ /*
+ * eg ndn: ou=People,o=icnc,o=sun.com
+ * match_this: ($dn),o=icnc,o=sun.com
+ *
+ * we can do a direct strncmp() because we know that
+ * there can be no "*" after the ($dn)...by definition.
+ */
+ if (strncasecmp( macro_suffix, &ndn[ndn_len-macro_suffix_len],
+ macro_suffix_len) != 0) {
+ return(NULL); /* suffix must match */
+ }
+ }
+ }
+
+ /* Start of the suffix in ndn...and it matched. */
+ ndn_suffix_start = (char*)&ndn[ndn_len-macro_suffix_len];
+
+ /* Here, macro_suffix is a suffix of ndn.
+ *
+ *
+ * Now, look at macro_prefix, if it is NULL, then ($dn) matches
+ * ndn[0..ndn_len-macro_suffix_len].
+ * (eg, ndn: cn=fred,ou=People,o=sun.com
+ * match_this: ($dn),o=sun.com.
+ *
+ */
+
+ macro_prefix = slapi_ch_strdup(match_this);
+
+ /* we know it's got a $(dn) */
+ tmp_ptr = strstr(macro_prefix, ACL_TARGET_MACRO_DN_KEY);
+ *tmp_ptr = '\0';
+ /* There may be a NULL prefix eg. match_this: ($dn),o=sun.com */
+ macro_prefix_len = strlen(macro_prefix);
+ if (macro_prefix_len == 0) {
+ slapi_ch_free((void **) &macro_prefix);
+ macro_prefix = NULL;
+ }
+
+ if (macro_prefix == NULL ) {
+ /*
+ * ($dn) matches ndn[0..ndn_len-macro_suffix_len]
+ */
+ int matched_val_len = 0;
+
+ matched_val_len = ndn_len-macro_suffix_len;
+
+ matched_val = (char *)slapi_ch_malloc(matched_val_len + 1);
+ strncpy(matched_val, ndn, ndn_len-macro_suffix_len);
+ /*
+ * Null terminate matched_val, removing trailing "," if there is
+ * one.
+ */
+ if (matched_val_len > 1) {
+ if (matched_val[matched_val_len-1] == ',' ) {
+ matched_val[matched_val_len-1] = '\0';
+ } else {
+ matched_val[matched_val_len] = '\0';
+ }
+ }
+ ret_val = matched_val;
+ } else {
+
+
+ /*
+ * If it is not NULL, then if macro_prefix contains a * then
+ * it needs to be an exact prefix of ndn (modulo the * component
+ * which matches anything) becuase that's the semantics
+ * of target patterns containing *'s, except that we just
+ * make it match one component.
+ * If it is such a prefix then ($dn) matches that portion of ndn
+ * from the end of the prefix, &ndn[ndn_prefix_end] to
+ * ndn_suffix_start.
+ * If ndn_prefix_len > ndn_len-macro_suffix_len then return(NULL),
+ * otherwise $(dn) matches ndn[ndn_prefix_len..ndn_len-macro_suffix_len].
+ *
+ *
+ * eg. ndn: cn=fred,ou=P,o=sun.com
+ * match_this: cn=*,($dn),o=sun.com
+ */
+
+ if ( strstr(macro_prefix, "=*") != NULL ) {
+ int exact_match = 0;
+
+ ndn_prefix_len = acl_match_prefix( macro_prefix, ndn, &exact_match);
+ if ( ndn_prefix_len != -1 ) {
+
+ /*
+ * ndn[0..ndn_prefix_len] is the prefix in ndn.
+ * ndn[ndn_prefix_len..ndn_len-macro_suffix_len] is the
+ * matched string.
+ */
+ if (ndn_prefix_len >= ndn_len-macro_suffix_len) {
+
+ /*
+ * eg ndn: cn=fred,ou=People,o=icnc,o=sun.com
+ * cn=*,ou=People,o=icnc,($dn),o=icnc,o=sun.com
+ */
+
+ ret_val = NULL; /* matched string is empty */
+ } else {
+
+ /*
+ * eg ndn: cn=fred,ou=People,o=icnc,o=sun.com
+ * cn=*,ou=People,($dn),o=sun.com
+ */
+
+ matched_val_len = ndn_len-macro_suffix_len-ndn_prefix_len;
+ matched_val = (char *)slapi_ch_malloc(matched_val_len + 1);
+ strncpy(matched_val, &ndn[ndn_prefix_len], matched_val_len);
+ if (matched_val_len > 1) {
+ if (matched_val[matched_val_len-1] == ',' ) {
+ matched_val[matched_val_len-1] = '\0';
+ } else {
+ matched_val[matched_val_len] = '\0';
+ }
+ }
+ matched_val[matched_val_len] = '\0';
+ ret_val = matched_val;
+ }
+ } else {
+ /* Was not a prefix so not a match */
+ ret_val = NULL;
+ }
+ } else {
+
+ /*
+ *
+ * If macro_prefix is not NULL and it does not
+ * contain a =* then
+ * we need to ensure that macro_prefix is a substring
+ * ndn.
+ * If it is and the position of the character after it's end in
+ * ndn is
+ * ndn_prefix_end then ($dn) matches
+ * ndn[ndn_prefix_end..ndn_len-macro_suffix_len].
+ *
+ *
+ * One important principal is that ($dn) matches a maximal
+ * chunk--this way it will serve to make the link
+ * between resources and users at each level of the structure.
+ *
+ * eg. ndn: ou=Groups,ou=Groups,ou=Groups,c=fr
+ * macro_prefix: ou=Groups,($dn),c=fr
+ *
+ * then ($dn) matches ou=Groups,ou=Groups.
+ *
+ *
+ *
+ * If it is not a substring, then there is no match.
+ * If it is a substring and
+ * ndn[ndn_prefix_end..ndn_len-macro_suffix_len] is empty then
+ * it's also not a match as we demand that ($dn) match a non-empty
+ * string.
+ *
+ *
+ *
+ * (eg. ndn: cn=fred,o=icnc,ou=People,o=sun.com
+ * match_this: o=icnc,($dn),o=sun.com.)
+ *
+ *
+ * (eg. ndn: cn=fred,o=menlo park,ou=People,o=icnc,o=sun.com
+ * match_this: o=menlo park,ou=People,($dn),o=sun.com
+ *
+ */
+
+ ndn_prefix_end = acl_strstr((char *)ndn, macro_prefix);
+ if ( ndn_prefix_end == -1) {
+ ret_val = NULL;
+ } else {
+ /* Is a substring */
+
+ ndn_prefix_end += macro_prefix_len;
+
+ /*
+ * make sure the matching part is non-empty:
+ *
+ * ndn[ndn_prefix_end..mndn_len-macro_suffix_len].
+ */
+
+ if ( ndn_prefix_end >= ndn_len-macro_suffix_len) {
+ ret_val = NULL;
+ } else {
+ /*
+ * ($dn) matches the non-empty string segment
+ * ndn[ndn_prefix_end..mndn_len-macro_suffix_len]
+ * the -1 is because macro_suffix_eln does not include
+ * the coma before the suffix.
+ */
+
+ matched_val_len = ndn_len-macro_suffix_len-
+ ndn_prefix_end - 1;
+
+ matched_val = (char *)slapi_ch_malloc(matched_val_len + 1);
+ strncpy(matched_val, &ndn[ndn_prefix_end],
+ matched_val_len);
+ matched_val[matched_val_len] = '\0';
+
+ ret_val = matched_val;
+ }
+ }
+ }/* contains an =* */
+ slapi_ch_free((void **) &macro_prefix);
+ }/* macro_prefix != NULL */
+
+ return(ret_val);
+}
+
+/*
+ * Checks to see if macro_prefix is an exact prefix of ndn.
+ * macro_prefix may contain a * component.
+ *
+ * The length of the matched prefix in ndn is returned.
+ * If it was not a match, a negative int is returned.
+ * Also, if the string matched exactly,
+ * exact_match is set to 1, other wise it was a proper prefix.
+ *
+*/
+
+int
+acl_match_prefix( char *macro_prefix, const char *ndn, int *exact_match) {
+
+ int macro_index = 0;
+ int ndn_index = 0;
+ int ret_code = -1;
+ char *curr_macro_component = NULL;
+ char *curr_ndn_component = NULL;
+ int matched = 0;
+ int macro_prefix_len = 0;
+ int ndn_len = 0;
+ int i = 0;
+ int j = 0;
+ int done = 0;
+ int t = 0;
+ char * tmp_str = NULL;
+ int k,l = 0;
+
+ *exact_match = 0; /* default to not an exact match */
+
+ /* The NULL prefix matches everthing*/
+ if (macro_prefix == NULL) {
+ if ( ndn == NULL ) {
+ *exact_match = 1;
+ }
+ return(0);
+ } else {
+ /* macro_prefix is not null, so if ndn is NULL, it's not a match. */
+ if ( ndn == NULL) {
+ return(-1);
+ }
+ }
+ /*
+ * Here, neither macro_prefix nor ndn are NULL.
+ *
+ * eg. macro_prefix: cn=*,ou=people,o=sun.com
+ * ndn : cn=fred,ou=people,o=sun.com
+ */
+
+
+ /*
+ * Here, there is a component with a * (eg. cn=* ) so
+ * we need to step through the macro_prefix, and where there is
+ * such a * match on that component,
+ * when we run out of * componenets, jsut do a straight match.
+ *
+ * Out of interest, the following extended regular expression
+ * will match just one ou rdn value from a string:
+ * "^uid=admin,ou=\([^,]*\\\,\)*[^,]*,o=sun.com$"
+ *
+ *
+ * eg. cn=fred,ou=People,o=sun.com
+ *
+ *
+ * s points to the = of the component.
+ */
+
+ macro_prefix_len = strlen(macro_prefix);
+ ndn_len = strlen(ndn);
+ i = 0;
+ j = 0;
+ done = 0;
+ while ( !done ) {
+
+ /* Here ndn[0..i] has matched macro_prefix[0..j] && j<= i
+ * i<=ndn_len j<=macro_prefix_len */
+
+ if ( (t = acl_strstr(&macro_prefix[j], "=*")) < 0 ) {
+ /*
+ * No more *'s, do a straight match on
+ * macro_prefix[j..macro_prefix_len] and
+ * ndn[i..macro_prefix_len]
+ */
+
+ if( macro_prefix_len-j > ndn_len-i) {
+ /* Not a prefix, nor a match */
+ *exact_match = 0;
+ ret_code = -1;
+ done = 1;
+ } else {
+ /*
+ * ndn_len-i >= macro_prefix_len - j
+ * if macro_prefix_len-j is 0, then
+ * it's a null prefix, so it matches.
+ * If in addition ndn_len-i is 0 then it's
+ * an exact match.
+ * Otherwise, do the cmp.
+ */
+
+ if ( macro_prefix_len-j == 0) {
+ done = 1;
+ ret_code = i;
+ if ( ndn_len-i == 0) {
+ *exact_match = 1;
+ }
+ }else {
+
+ if (strncasecmp(&macro_prefix[j], &ndn[i],
+ macro_prefix_len-j) == 0) {
+ *exact_match = (macro_prefix_len-j == ndn_len-i);
+ ret_code = i + macro_prefix_len -j;
+ done = 1;
+ } else {
+ /* not a prefix not a match */
+ *exact_match = 0;
+ ret_code = -1;
+ done = 1;
+ }
+ }
+ }
+ }else {
+ /*
+ * Is another * component, so:
+ * 1. match that component in macro_prefix (at pos k say)
+ * with the corresponding compoent (at pos l say ) in ndn
+ *
+ * 2. match the intervening string ndn[i..l] and
+ * macro_prefix[j..k].
+ */
+
+ /* First, find the start of the component in macro_prefix. */
+
+ t++; /* move to the--this way we will look for "ou=" in ndn */
+ k = acl_find_comp_start(macro_prefix, t);
+
+ /* Now identify that component in ndn--if it's not there no match */
+ tmp_str = slapi_ch_malloc(t-k+1);
+ strncpy(tmp_str, &macro_prefix[k], t-k);
+ tmp_str[t-k] = '\0';
+ l = acl_strstr((char*)&ndn[i], tmp_str);
+ if (l == -1) {
+ *exact_match = 0;
+ ret_code = -1;
+ done = 1;
+ } else {
+ /*
+ * Found the comp in ndn, so the comp matches.
+ * Now test the intervening string segments:
+ * ndn[i..l] and macro_prefix[j..k]
+ */
+
+ if ( k-j != l-i ) {
+ *exact_match = 0;
+ ret_code = -1;
+ done = 1;
+ } else{
+ if (strncasecmp(&macro_prefix[j], &ndn[i], k-j) != 0) {
+ *exact_match = 0;
+ ret_code = -1;
+ done = 1;
+ } else {
+ /* Matched, so bump i and j and keep going.*/
+ i += acl_find_comp_end((char*)&ndn[l]);
+ j += acl_find_comp_end((char*)&macro_prefix[k]);
+ }
+ }
+ }
+ slapi_ch_free((void **)&tmp_str);
+ }
+ }/* while */
+
+ return(ret_code);
+
+}
+
+/*
+ * returns the index in s of where the component at position
+ * s[pos] starts.
+ * This is the index of the character after the first unescaped comma
+ * moving backwards in s from pos.
+ * If this is not found then return 0, ie. the start of the string.
+ * If the index returned is > strlen(s) then could not find it.
+ * only such case is if you pass ",", in which case there is no component start.
+*/
+
+static int
+acl_find_comp_start(char * s, int pos ) {
+
+ int i =0;
+ int comp_start = 0;
+
+ i = pos;
+ while( i > 0 && (s[i] != ',' ||
+ s[i-1] == '\\')) {
+ i--;
+ }
+ /*
+ * i == 0 || (s[i] == ',' && s[i-1] != '\\')
+ */
+ if (i==0) {
+ /* Got all the way with no unescaped comma */
+ if (s[i] == ',') {
+ comp_start = i+1;
+ } else {
+ comp_start = i;
+ }
+ } else { /* Found an unescaped comma */
+ comp_start = i + 1;
+ }
+
+ return( comp_start);
+}
+
+/*
+ * returns the index in s of the first character after the
+ * first unescaped comma.
+ * If ther is no such character, returns strlen(s);
+*/
+
+int
+acl_find_comp_end( char * s) {
+
+ int i = 0;
+ int s_len = 0;
+
+ s_len = strlen(s);
+
+ if ( s_len == 0 || s_len == 1) {
+ return(s_len);
+ }
+
+ /* inv: i+1<s_len && (s[i] == '\\' || s[i+1] != ',')*/
+
+ i = 0;
+ while( i+1 < s_len && (s[i] == '\\' ||
+ s[i+1] != ',')) {
+ i++;
+ }
+ if ( i + 1 == s_len) {
+ return(s_len);
+ } else {
+ return(i+2);
+ }
+}
+
+/*
+ * return the index in s where substr occurs, if none
+ * returns -1.
+*/
+
+int
+acl_strstr(char * s, char *substr) {
+
+ char *t = NULL;
+ char *tmp_str = NULL;
+
+ tmp_str = slapi_ch_strdup(s);
+
+ if ( (t = strstr(tmp_str, substr)) == NULL ) {
+ slapi_ch_free((void **)&tmp_str);
+ return(-1);
+ } else {
+ int l = 0;
+ *t = '\0';
+ l = strlen(tmp_str);
+ slapi_ch_free((void **)&tmp_str);
+ return(l);
+ }
+}
+
+/*
+ * replace all occurences of substr in s with replace_str.
+ *
+ * returns a malloced version of the patched string.
+*/
+
+char *
+acl_replace_str(char * s, char *substr, char* replace_with_str) {
+
+ char *str = NULL;
+ char *working_s, *suffix, *prefix, *patched;
+ int replace_with_len, substr_len, prefix_len, suffix_len;
+
+ if ( (str = strstr(s, substr)) == NULL) {
+ return(slapi_ch_strdup(s));
+ } else {
+
+
+ replace_with_len = strlen(replace_with_str);
+ substr_len = strlen(substr);
+
+ working_s = slapi_ch_strdup(s);
+ prefix = working_s;
+ str = strstr(prefix, substr);
+
+ while (str != NULL) {
+
+ /*
+ * working_s is a copy of the string to be patched
+ * str points to a substr to be patched
+ * prefix points to working_s
+ */
+
+ *str = '\0';
+
+ suffix = &str[substr_len];
+ prefix_len = strlen(prefix);
+ suffix_len = strlen(suffix);
+
+ patched = (char *)slapi_ch_malloc(prefix_len +
+ replace_with_len +
+ suffix_len +1 );
+ strcpy(patched, prefix);
+ strcat(patched, replace_with_str);
+ strcat(patched, suffix);
+
+ slapi_ch_free((void **)&working_s);
+
+ working_s = patched;
+ prefix = working_s;
+ str = strstr(prefix, substr);
+
+ }
+
+ return(working_s);
+ }
+
+}
+
+
+/*
+ * Start at index and return a malloced string that is the
+ * next component in dn (eg. "ou=People"),
+ * or NULL if couldn't find the next one.
+*/
+
+char *
+get_next_component(char *dn, int *index) {
+
+ int dn_len = strlen(dn);
+ int start_next = -1;
+ int i = 0;
+ char *ret_comp;
+
+ if (*index>= dn_len) {
+ return(NULL);
+ }
+
+ start_next = acl_find_comp_end( &dn[*index]);
+
+ if ( start_next >= dn_len ) {
+ *index = start_next;
+ return(NULL); /* no next comp */
+ }
+
+ /*
+ *Here, start_next should be the start of the next
+ * component--so far have not run off the end.
+ */
+
+ i = acl_find_comp_end( &dn[start_next]);
+
+ /*
+ * Here, the matched string is all from start_next to i.
+ */
+
+ ret_comp = (char *)slapi_ch_malloc(i - start_next +1);
+ memcpy( ret_comp, &dn[start_next], i-start_next);
+ ret_comp[i-start_next] = '\0';
+
+ return(ret_comp);
+}
+
+char *
+get_this_component(char *dn, int *index) {
+
+ int dn_len = strlen(dn);
+ int i = 0;
+ char *ret_comp;
+
+ if (*index>= dn_len) {
+ return(NULL);
+ }
+
+ if (dn_len == *index + 1) {
+ /* Just return a copy of the string. */
+ return(slapi_ch_strdup(dn));
+ }else {
+ /* *index + 1 < dn_len */
+ i = *index+1;
+ while( (dn[i] != '\0') && dn[i] != ',' && dn[i-1] != '\\') {
+ i += 1;
+ }
+
+ /*
+ * Here, the matched string is all from *index to i.
+ */
+
+ ret_comp = (char *)slapi_ch_malloc(i - *index +1);
+ memcpy( ret_comp, &dn[*index], i - *index);
+ ret_comp[i-*index] = '\0';
+
+ if (i < dn_len) {
+ /* Found a comma before the end */
+ *index = i + 1; /* skip it */
+ }
+
+ return(ret_comp);
+ }
+
+}
+
+/*
+ * return 1 if comp1==comp2,
+ * return 0 otherwise.
+ *
+ * the components might have *'s.
+ *
+ * eg: comp1: cn=*
+ * comp2: cn=fred
+ *
+ *
+*/
+
+static int
+aclutil_compare_components( char * comp1, char *comp2) {
+
+ char *tmp_str = NULL;
+
+ tmp_str = strstr( comp1, "=*");
+ if ( tmp_str == NULL) {
+
+ /* Just a straight cmp */
+
+ if (slapi_utf8casecmp((ACLUCHP)comp1, (ACLUCHP)comp2) == 0) {
+ return(1);
+ } else {
+ return(0);
+ }
+ } else {
+
+ char *tmp_comp1= NULL;
+ char *tmp_comp2 = NULL;
+ int ret_code = 0;
+
+ /* Here, just compare the bit before the = */
+
+ tmp_comp1 = slapi_ch_strdup(comp1);
+ tmp_comp2 = slapi_ch_strdup(comp2);
+
+ /*
+ * Probably need to verify it's not escaped--see code for looking for
+ * unescaped commas.
+ */
+
+ tmp_str = strstr(tmp_comp1, "=");
+ *tmp_str = '\0';
+
+ tmp_str = strstr(tmp_comp2, "=");
+ if ( tmp_str == NULL) {
+ ret_code = 0;
+ } else{
+
+ *tmp_str = '\0';
+
+ if (slapi_utf8casecmp((ACLUCHP)comp1, (ACLUCHP)comp2) == 0) {
+ ret_code = 1;
+ } else {
+ ret_code = 0;
+ }
+
+ slapi_ch_free((void **)&tmp_comp1);
+ slapi_ch_free((void **)&tmp_comp2);
+
+ return(ret_code);
+
+ }
+
+ }
+}
+
+/*
+ * return a pointer to the final component of macro_prefix.
+*/
+
+static char *
+acl_get_final_component(char *macro_prefix) {
+
+ return(NULL);
+}
+
+/*
+ *
+ *
+*/
+
+static char *
+acl_match_component( char *start, char *component) {
+
+
+ return(NULL);
+}
+
+/* acl hash table funcs */
+
+/*
+ * Add the key adn value to the ht.
+ * If it already exists then remove the old one and free
+ * the value.
+*/
+void acl_ht_add_and_freeOld(acl_ht_t * acl_ht,
+ PLHashNumber key,
+ char *value){
+ char *old_value = NULL;
+
+ if ( (old_value = (char *)acl_ht_lookup( acl_ht, key)) != NULL ) {
+ acl_ht_remove( acl_ht, key);
+ slapi_ch_free((void **)&old_value);
+ }
+
+ PL_HashTableAdd( acl_ht, (const void *)key, value);
+}
+
+/*
+ * Return a new acl_ht_t *
+*/
+acl_ht_t *acl_ht_new(void) {
+
+ return(PL_NewHashTable(30, acl_ht_hash, /* key hasher */
+ PL_CompareValues, /* keyCompare */
+ PL_CompareStrings, 0, 0)); /* value compare */
+}
+
+static PLHashNumber acl_ht_hash( const void *key) {
+
+ return( (PLHashNumber)key );
+}
+
+/* Free all the values in the ht */
+void acl_ht_free_all_entries_and_values( acl_ht_t *acl_ht) {
+
+ PL_HashTableEnumerateEntries( acl_ht, acl_ht_free_entry_and_value,
+ NULL);
+}
+
+static PRIntn
+acl_ht_free_entry_and_value(PLHashEntry *he, PRIntn i, void *arg)
+{
+
+ slapi_ch_free((void **)&he->value); /* free value */
+
+ /* Free this entry anfd go on to next one */
+ return ( HT_ENUMERATE_NEXT | HT_ENUMERATE_REMOVE);
+}
+
+/* Free all the values in the ht */
+void acl_ht_display_ht( acl_ht_t *acl_ht) {
+
+#ifdef DEBUG
+ PL_HashTableEnumerateEntries( acl_ht, acl_ht_display_entry, NULL);
+#endif
+}
+
+static PRIntn
+acl_ht_display_entry(PLHashEntry *he, PRIntn i, void *arg)
+{
+ PLHashNumber aci_index = (PLHashNumber)he->key;
+ char *matched_val = (char *)he->value;
+
+ LDAPDebug(LDAP_DEBUG_ACL,"macro ht entry: key='%d' matched_val='%s'"
+ "keyhash='%d'\n",
+ aci_index, (matched_val ? matched_val: "NULL"),
+ (PLHashNumber)he->keyHash);
+
+ return HT_ENUMERATE_NEXT;
+
+}
+
+/* remove this entry from the ht--doesn't free the value.*/
+void acl_ht_remove( acl_ht_t *acl_ht, PLHashNumber key) {
+
+ PL_HashTableRemove( acl_ht, (const void *)key);
+}
+
+/* Retrieve a pointer to the value of the entry with key */
+void *acl_ht_lookup( acl_ht_t *acl_ht,
+ PLHashNumber key) {
+
+ return( PL_HashTableLookup( acl_ht, (const void *)key) );
+}
+
+
+/***************************************************************************/
+/* E N D */
+/***************************************************************************/
+
diff --git a/ldap/servers/plugins/acl/libacl.def b/ldap/servers/plugins/acl/libacl.def
new file mode 100644
index 00000000..947638cd
--- /dev/null
+++ b/ldap/servers/plugins/acl/libacl.def
@@ -0,0 +1,16 @@
+; BEGIN COPYRIGHT BLOCK
+; Copyright 2001 Sun Microsystems, Inc.
+; Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+; All rights reserved.
+; END COPYRIGHT BLOCK
+;
+;
+;
+DESCRIPTION 'Netscape Directory Server 7.0 ACL Plugin'
+;CODE SHARED READ EXECUTE
+;DATA SHARED READ WRITE
+EXPORTS
+ acl_preopInit @1
+; unused @2
+ acl_init @3
+ plugin_init_debug_level @4
diff --git a/ldap/servers/plugins/chainingdb/Makefile b/ldap/servers/plugins/chainingdb/Makefile
new file mode 100644
index 00000000..4f47e480
--- /dev/null
+++ b/ldap/servers/plugins/chainingdb/Makefile
@@ -0,0 +1,93 @@
+#
+# BEGIN COPYRIGHT BLOCK
+# Copyright 2001 Sun Microsystems, Inc.
+# Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+# All rights reserved.
+# END COPYRIGHT BLOCK
+#
+#
+# GNU Makefile for Directory Server "Chaining Backend" plugin
+#
+
+LDAP_SRC = ../../..
+
+MCOM_ROOT = ../../../../..
+
+NOSTDCLEAN=true # don't let nsconfig.mk define target clean
+NOSTDSTRIP=true # don't let nsconfig.mk define target strip
+NSPR20=true # probably should be defined somewhere else (not sure where)
+
+OBJDEST = $(OBJDIR)/lib/libcb
+LIBDIR = $(LIB_RELDIR)
+
+include $(MCOM_ROOT)/ldapserver/nsconfig.mk
+include $(LDAP_SRC)/nsldap.mk
+
+ifeq ($(ARCH), WINNT)
+DEF_FILE:=./libcb.def
+endif
+
+CFLAGS+=$(SLCFLAGS)
+
+INCLUDES += -I$(LDAP_SRC)/servers/slapd
+
+CB_OBJS= cb_temp.o cb_init.o cb_config.o cb_instance.o cb_start.o cb_search.o cb_utils.o cb_add.o cb_delete.o cb_schema.o \
+cb_acl.o cb_modify.o cb_compare.o cb_modrdn.o cb_abandon.o cb_conn_stateless.o cb_bind.o cb_unbind.o cb_monitor.o \
+cb_controls.o cb_size.o cb_test.o cb_close.o cb_cleanup.o cb_debug.o
+
+OBJS = $(addprefix $(OBJDEST)/, $(CB_OBJS))
+
+ifeq ($(ARCH), WINNT)
+LIBCB_DLL_OBJ = $(addprefix $(OBJDEST)/, cbdllmain.o)
+endif
+
+LIBCB= $(addprefix $(LIBDIR)/, $(CB_DLL).$(DLL_SUFFIX))
+
+ifeq ($(ARCH), WINNT)
+EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAP_LIBUTIL_DEP) $(LDAP_COMMON_LIBS_DEP)
+EXTRA_LIBS_DEP += $(LDAPSDK_DEP) $(SECURITY_DEP) $(NSPR_DEP)
+EXTRA_LIBS += $(LIBSLAPD) $(LDAP_SDK_LIBLDAP_DLL) $(LDAP_LIBUTIL) $(LDAP_COMMON_LIBS) $(SECURITYLINK) $(NSPRLINK)
+
+endif
+
+
+ifeq ($(ARCH), WINNT)
+DLL_LDFLAGS += -def:"./libcb.def"
+endif # WINNT
+
+ifeq ($(ARCH), AIX)
+EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAP_LIBUTIL_DEP) $(LDAP_COMMON_LIBS_DEP)
+EXTRA_LIBS_DEP += $(LDAPSDK_DEP) $(SECURITY_DEP) $(NSPR_DEP)
+EXTRA_LIBS += $(LIBSLAPD) $(LDAP_SDK_LIBLDAP_DLL) $(LIBUTIL) $(LDAP_COMMON_LIBS) $(SECURITYLINK) $(NSPRLINK)
+EXTRA_LIBS += $(DLL_EXTRA_LIBS)
+LD=ld
+endif
+
+ifeq ($(ARCH), HPUX)
+EXTRA_LIBS_DEP += $(LDAPSDK_DEP) $(NSPR_DEP) $(SECURITY_DEP)
+EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(SECURITYLINK) $(NSPRLINK) $(ICULINK)
+endif
+
+clientSDK:
+
+all: $(OBJDEST) $(LIBDIR) $(LIBCB)
+
+$(LIBCB): $(OBJS) $(LIBCB_DLL_OBJ) $(DEF_FILE)
+ $(LINK_DLL) $(LIBCB_DLL_OBJ) $(PLATFORMLIBS) $(EXTRA_LIBS)
+
+veryclean: clean
+
+clean:
+ $(RM) $(OBJS)
+ifeq ($(ARCH), WINNT)
+ $(RM) $(LIBCB_DLL_OBJ)
+endif
+ $(RM) $(LIBCB)
+
+$(OBJDEST):
+ $(MKDIR) $(OBJDEST)
+
+#
+# header file dependencies (incomplete)
+#
+$(OBJS): cb.h
diff --git a/ldap/servers/plugins/chainingdb/cb.h b/ldap/servers/plugins/chainingdb/cb.h
new file mode 100644
index 00000000..8aed412c
--- /dev/null
+++ b/ldap/servers/plugins/chainingdb/cb.h
@@ -0,0 +1,461 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#ifndef CBHFILE
+#define CBHFILE
+
+/*** #define CB_YIELD ***/
+
+#include <stdio.h>
+#include <string.h>
+#include <prlock.h>
+#include <prcvar.h>
+#include "slapi-plugin.h"
+#include "slapi-private.h"
+#include "portable.h"
+#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */
+#include "dirver.h"
+
+/* Constants */
+
+#define CB_DIRECTORY_MANAGER_DN "cn=directory manager"
+#define CB_CHAINING_BACKEND_TYPE "chaining database"
+#define CB_PLUGIN_NAME "chaining database"
+#define CB_PLUGIN_SUBSYSTEM "chaining database"
+#define CB_PLUGIN_DESCRIPTION "LDAP chaining backend database plugin"
+
+#define CB_LDAP_SECURE_PORT 636
+#define CB_BUFSIZE 2048
+
+
+/* Macros */
+
+#define CB_LDAP_CONN_ERROR( err ) ( (err) == LDAP_SERVER_DOWN || \
+ (err) == LDAP_CONNECT_ERROR )
+#define CB_ASSERT( expr ) PR_ASSERT( expr )
+
+/* Innosoft chaining extension for loop detection */
+
+#define CB_LDAP_CONTROL_CHAIN_SERVER "1.3.6.1.4.1.1466.29539.12"
+
+/* Chaining backend configuration attributes */
+
+/* Monitor entry */
+#define CB_MONITOR_EXTENSIBLEOCL "extensibleObject"
+#define CB_MONITOR_INSTNAME "cn"
+#define CB_MONITOR_ADDCOUNT "nsAddCount"
+#define CB_MONITOR_DELETECOUNT "nsDeleteCount"
+#define CB_MONITOR_MODIFYCOUNT "nsModifyCount"
+#define CB_MONITOR_MODRDNCOUNT "nsRenameCount"
+#define CB_MONITOR_SEARCHBASECOUNT "nsSearchBaseCount"
+#define CB_MONITOR_SEARCHONELEVELCOUNT "nsSearchOneLevelCount"
+#define CB_MONITOR_SEARCHSUBTREECOUNT "nsSearchSubtreeCount"
+#define CB_MONITOR_ABANDONCOUNT "nsAbandonCount"
+#define CB_MONITOR_BINDCOUNT "nsBindCount"
+#define CB_MONITOR_UNBINDCOUNT "nsUnbindCount"
+#define CB_MONITOR_COMPARECOUNT "nsCompareCount"
+#define CB_MONITOR_OUTGOINGCONN "nsOpenOpConnectionCount"
+#define CB_MONITOR_OUTGOINGBINDCOUNT "nsOpenBindConnectionCount"
+
+/* Global configuration */
+#define CB_CONFIG_GLOBAL_FORWARD_CTRLS "nsTransmittedControls"
+#define CB_CONFIG_GLOBAL_CHAINING_COMPONENTS "nsActiveChainingComponents"
+#define CB_CONFIG_GLOBAL_CHAINABLE_COMPONENTS "nsPossibleChainingComponents"
+/* not documented */
+#define CB_CONFIG_GLOBAL_DEBUG "nsDebug"
+
+
+/* Instance-specific configuration */
+#define CB_CONFIG_CHAINING_COMPONENTS CB_CONFIG_GLOBAL_CHAINING_COMPONENTS
+#define CB_CONFIG_EXTENSIBLEOCL "extensibleObject"
+/* XXXSD to be changed */
+#define CB_CONFIG_INSTANCE_FILTER "(objectclass=nsBackendInstance)"
+#define CB_CONFIG_INSTNAME "cn"
+#define CB_CONFIG_SUFFIX "nsslapd-suffix"
+#define CB_CONFIG_SIZELIMIT "nsslapd-sizelimit"
+#define CB_CONFIG_TIMELIMIT "nsslapd-timelimit"
+#define CB_CONFIG_HOSTURL "nsFarmServerURL"
+
+#define CB_CONFIG_BINDUSER "nsMultiplexorBindDn"
+#define CB_CONFIG_USERPASSWORD "nsMultiplexorCredentials"
+#define CB_CONFIG_MAXBINDCONNECTIONS "nsBindConnectionsLimit"
+#define CB_CONFIG_MAXCONNECTIONS "nsOperationConnectionsLimit"
+#define CB_CONFIG_MAXCONCURRENCY "nsConcurrentOperationsLimit"
+#define CB_CONFIG_MAXBINDCONCURRENCY "nsConcurrentBindLimit"
+
+#define CB_CONFIG_IMPERSONATION "nsProxiedAuthorization"
+
+#define CB_CONFIG_BINDTIMEOUT "nsBindTimeout"
+#define CB_CONFIG_TIMEOUT "nsOperationTimeout"
+#define CB_CONFIG_MAX_IDLE_TIME "nsMaxResponseDelay"
+#define CB_CONFIG_MAX_TEST_TIME "nsMaxTestResponseDelay"
+
+#define CB_CONFIG_REFERRAL "nsReferralOnScopedSearch"
+
+#define CB_CONFIG_CONNLIFETIME "nsConnectionLife"
+#define CB_CONFIG_ABANDONTIMEOUT "nsAbandonedSearchCheckInterval "
+#define CB_CONFIG_BINDRETRY "nsBindRetryLimit"
+#define CB_CONFIG_LOCALACL "nsCheckLocalACI"
+#define CB_CONFIG_HOPLIMIT "nsHopLimit"
+
+/* not documented */
+#define CB_CONFIG_ILLEGAL_ATTRS "nsServerDefinedAttributes"
+
+/* Default configuration values (as string) */
+
+/*
+ * CB_DEF_MAXCONNECTIONS and CB_DEF_MAXCONCURRENCY used to be 10.
+ * Reduced CB_DEF_MAXCONCURRENCY to 2 to workaround bug 623793 -
+ * err=1 in accesslogs and ber parsing errors in errors logs.
+ */
+#define CB_DEF_MAXCONNECTIONS "20" /* CB_CONFIG_MAXCONNECTIONS */
+#define CB_DEF_MAXCONCURRENCY "2" /* CB_CONFIG_MAXCONCURRENCY */
+#define CB_DEF_BIND_MAXCONNECTIONS "3" /* CB_CONFIG_MAXBINDCONNECTIONS */
+#define CB_DEF_BIND_MAXCONCURRENCY "10" /* CB_CONFIG_MAXBINDCONCURRENCY */
+#define CB_DEF_BINDTIMEOUT "15" /* CB_CONFIG_BINDTIMEOUT */
+#define CB_DEF_CONNLIFETIME "0" /* CB_CONFIG_CONNLIFETIME */
+#define CB_DEF_IMPERSONATION "on" /* CB_CONFIG_IMPERSONATION */
+#define CB_DEF_SEARCHREFERRAL "off" /* CB_CONFIG_REFERRAL */
+#define CB_DEF_ABANDON_TIMEOUT "1" /* CB_CONFIG_ABANDONTIMEOUT */
+#define CB_DEF_BINDRETRY "3" /* CB_CONFIG_BINDRETRY */
+#define CB_DEF_LOCALACL "off" /* CB_CONFIG_LOCALACL */
+#define CB_DEF_TIMELIMIT "3600"
+#define CB_DEF_SIZELIMIT "2000"
+#define CB_DEF_HOPLIMIT "10" /* CB_CONFIG_HOPLIMIT */
+#define CB_DEF_MAX_IDLE_TIME "60" /* CB_CONFIG_MAX_IDLE_TIME */
+#define CB_DEF_MAX_TEST_TIME "15" /* CB_CONFIG_MAX_TEST_TIME */
+
+typedef void *cb_config_get_fn_t(void *arg);
+typedef int cb_config_set_fn_t(void *arg, void *value, char *errorbuf, int phase, int apply);
+typedef struct _cb_instance_config_info {
+ char *config_name;
+ int config_type;
+ char *config_default_value;
+ cb_config_get_fn_t *config_get_fn;
+ cb_config_set_fn_t *config_set_fn;
+ int config_flags;
+} cb_instance_config_info;
+
+#define CB_CONFIG_TYPE_ONOFF 1 /* val = (int) value */
+#define CB_CONFIG_TYPE_STRING 2 /* val = (char *) value - The get functions
+ * for this type must return alloced memory
+ * that should be freed by the caller. */
+#define CB_CONFIG_TYPE_INT 3 /* val = (int) value */
+#define CB_CONFIG_TYPE_LONG 4 /* val = (long) value */
+#define CB_CONFIG_TYPE_INT_OCTAL 5 /* Same as CONFIG_TYPE_INT, but shown in octal*/
+#define CB_PREVIOUSLY_SET 1
+#define CB_ALWAYS_SHOW 2
+#define CB_CONFIG_PHASE_INITIALIZATION 1
+#define CB_CONFIG_PHASE_STARTUP 2
+#define CB_CONFIG_PHASE_RUNNING 3
+#define CB_CONFIG_PHASE_INTERNAL 4
+
+/*jarnou: default amount of time in seconds during wich the chaining backend will be unavailable */
+#define CB_UNAVAILABLE_PERIOD 30 /* CB_CONFIG_UNAVAILABLE_PERIOD */
+#define CB_INFINITE_TIME 360000 /* must be enough ... */
+/*jarnou: default number of connections failed from which the farm is declared unavailable */
+#define CB_NUM_CONN_BEFORE_UNAVAILABILITY 1
+#define FARMSERVER_UNAVAILABLE 1
+#define FARMSERVER_AVAILABLE 0
+
+/* Internal data structures */
+
+/* cb_backend represents the chaining backend type. */
+/* Only one instance is created when the plugin is */
+/* loaded. Contain global conf */
+typedef struct _cb_backend {
+
+ /*
+ ** keep track of plugin identity.
+ ** Used for internal operations
+ */
+
+ void *identity;
+ char * pluginDN;
+ char * configDN;
+
+ /*
+ ** There are times when we need a pointer to the chaining database
+ ** plugin, so we will store a pointer to it here. Examples of
+ ** when we need it are when we create a new instance and when
+ ** we need the name of the plugin to do internal ops.
+ */
+
+ struct slapdplugin *plugin;
+
+ /*
+ ** Global config. shared by all chaining db instances
+ */
+
+ struct {
+ char ** forward_ctrls; /* List of forwardable controls */
+ char ** chaining_components; /* List of plugins that chains */
+ char ** chainable_components; /* List of plugins allowed to chain*/
+ /* internal operations. */
+ PRRWLock *rwl_config_lock; /* Protect the global config */
+ } config;
+
+ int started; /* TRUE when started */
+
+} cb_backend;
+
+
+/* Connection management */
+
+/* states */
+#define CB_CONNSTATUS_OK 1 /* Open */
+#define CB_CONNSTATUS_DOWN 2 /* Down */
+#define CB_CONNSTATUS_STALE 3
+
+#define ENABLE_MULTITHREAD_PER_CONN 1 /* to allow multiple threads to perform LDAP operations on a connection */
+#define DISABLE_MULTITHREAD_PER_CONN 0 /* to allow only one thread to perform LDAP operations on a connection */
+
+/************** WARNING: Be careful if you want to change this constant. It is used in hexadecimal in cb_conn_stateless.c in the function PR_ThreadSelf() ************/
+#define MAX_CONN_ARRAY 2048 /* we suppose the number of threads in the server not to exceed this limit*/
+/**********************************************************************************************************/
+typedef struct _cb_outgoing_conn{
+ LDAP *ld;
+ unsigned long refcount;
+ struct _cb_outgoing_conn *next;
+ time_t opentime;
+ int status;
+ int ThreadId ; /* usefull to identify the thread when SSL is enabled */
+} cb_outgoing_conn;
+
+typedef struct {
+ char *hostname; /* Farm server name */
+ char *url;
+ unsigned int port;
+ int secure;
+ char *binddn; /* normalized */
+ char *binddn2; /* not normalized, value returned to the client */
+ char *password;
+ int bindit; /* If true, open AND bind */
+ char ** waste_basket; /* stale char * */
+
+ struct {
+ unsigned int maxconnections;
+ unsigned int maxconcurrency;
+ unsigned int connlifetime;
+ struct timeval op_timeout;
+ struct timeval bind_timeout;
+
+ Slapi_Mutex *conn_list_mutex;
+ Slapi_CondVar *conn_list_cv;
+ cb_outgoing_conn *conn_list;
+ unsigned int conn_list_count;
+
+ } conn;
+
+ cb_outgoing_conn *connarray[MAX_CONN_ARRAY]; /* array of secure connections */
+
+ /* To protect the config set by LDAP */
+ PRRWLock * rwl_config_lock;
+} cb_conn_pool;
+
+
+/* _cb_backend_instance represents a instance of the chaining */
+/* backend. */
+
+typedef struct _cb_backend_instance {
+
+ char *inst_name; /* Unique name */
+ Slapi_Backend *inst_be; /* Slapi_Bakedn associated with it */
+ cb_backend *backend_type; /* pointer to the backend type */
+
+ /* configuration */
+
+ PRRWLock *rwl_config_lock; /* protect the config */
+ char *configDn; /* config entry dn */
+ char *monitorDn; /* monitor entry dn */
+ int local_acl; /* True if local acl evaluation */
+ /* sometimes a chaining backend may be associated with a local backend
+ 1) The chaining backend is the backend of a sub suffix, and the
+ parent suffix has a local backend
+ 2) Entry distribution is being used to distribute write operations to
+ a chaining backend and other operations to a local backend
+ (e.g. a replication hub or consumer)
+ If the associated local backend is being initialized (import), it will be
+ disabled, and it will be impossible to evaluate local acls. In this case,
+ we still want to be able to chain operations to a farm server or another
+ database chain. But the current code will not allow cascading without
+ local acl evaluation (cb_controls.c). The following variable allows us to relax that
+ restriction while the associated backend is disabled
+ */
+ int associated_be_is_disabled; /* true if associated backend is disabled */
+ int isconfigured; /* True when valid config entry */
+ int impersonate; /* TRUE to impersonate users */
+ int searchreferral; /* TRUE to return referral for scoped searches */
+ int bind_retry;
+ struct timeval abandon_timeout; /* check for abandoned op periodically */
+ struct timeval op_timeout;
+ char **url_array; /* list of urls to farm servers */
+ char **chaining_components; /* List of plugins using chaining */
+ char **illegal_attributes; /* Attributes not forwarded */
+ char **every_attribute; /* attr list to get every attr, including op attrs */
+ int sizelimit;
+ int timelimit;
+ int hoplimit;
+ int max_idle_time; /* how long we wait before pinging the farm server */
+ int max_test_time; /* how long we wait during ping */
+
+ cb_conn_pool *pool; /* Operation cnx pool */
+ cb_conn_pool *bind_pool; /* Bind cnx pool */
+
+ Slapi_Eq_Context eq_ctx; /* Use to identify the function put in the queue */
+
+ /* Monitoring */
+
+ struct {
+ Slapi_Mutex *mutex;
+ unsigned long addcount;
+ unsigned long deletecount;
+ unsigned long modifycount;
+ unsigned long modrdncount;
+ unsigned long searchbasecount;
+ unsigned long searchonelevelcount;
+ unsigned long searchsubtreecount;
+ unsigned long abandoncount;
+ unsigned long bindcount;
+ unsigned long unbindcount;
+ unsigned long comparecount;
+ } monitor;
+
+ /* Monitoring the chaining BE availability */
+ /* Principle: as soon as we detect an abnormal pb with an ldap operation, and we close the connection
+ or if we can't open a connection, we increment a counter (cpt). This counter represents the number of
+ continuously pbs we can notice. Before forwarding an LDAP operation, wether the farmserver is available or not,
+ through the value of the counter. If the farmserver is not available, we just return an error msg to the client */
+
+ struct {
+ int unavailable_period ; /* how long we wait as soon as the farm is declared unavailable */
+ int max_num_conn_failed ; /* max number of consecutive failed/aborted connections before we declared the farm as unreachable */
+ time_t unavailableTimeLimit ; /* time from which the chaining BE becomes available */
+ int farmserver_state ; /* FARMSERVER_AVAILABLE if the chaining is available, FARMSERVER_UNAVAILABLE else */
+ int cpt ; /* count the number of consecutive failed/aborted connexions */
+ Slapi_Mutex *cpt_lock ; /* lock to protect the counter cpt */
+ Slapi_Mutex *lock_timeLimit ; /* lock to protect the unavailableTimeLimit variable*/
+ } monitor_availability;
+
+
+} cb_backend_instance;
+
+/* Data structure for the search operation to carry candidates */
+
+#define CB_SEARCHCONTEXT_ENTRY 2
+
+typedef struct _cb_searchContext {
+ int type;
+ void *data;
+ int msgid;
+ LDAP *ld;
+ cb_outgoing_conn *cnx;
+ Slapi_Entry *tobefreed;
+ LDAPMessage *pending_result;
+ int pending_result_type;
+} cb_searchContext;
+
+#define CB_REOPEN_CONN -1968 /* Different from any LDAP_XXX errors */
+
+/* Forward declarations */
+
+/* for ctrl_flags on cb_update_controls */
+#define CB_UPDATE_CONTROLS_ADDAUTH 1
+#define CB_UPDATE_CONTROLS_ISABANDON 2
+
+
+int cb_get_connection(cb_conn_pool * pool, LDAP ** ld, cb_outgoing_conn ** cnx, struct timeval * tmax, char **errmsg);
+int cb_config(cb_backend_instance * cb, int argc, char ** argv );
+int cb_update_controls( Slapi_PBlock *pb, LDAP * ld, LDAPControl *** controls, int ctrl_flags);
+int cb_is_control_forwardable(cb_backend * cb, char *controloid);
+int cb_access_allowed (Slapi_PBlock *pb,Slapi_Entry *e,char *type,struct berval * bval, int op, char ** buf);
+int cb_forward_operation(Slapi_PBlock * op);
+int cb_parse_instance_config_entry(cb_backend * cb, Slapi_Entry * e);
+int cb_abandon_connection(cb_backend_instance * cb, Slapi_PBlock * pb, LDAP ** ld);
+int cb_atoi(char *str);
+int cb_check_forward_abandon(cb_backend_instance * cb,Slapi_PBlock * pb, LDAP * ld, int msgid );
+int cb_search_monitor_callback(Slapi_PBlock *pb, Slapi_Entry *e, Slapi_Entry *e2, int *ret, char *t,void *a);
+int cb_config_load_dse_info(Slapi_PBlock * pb);
+int cb_config_add_dse_entries(cb_backend *cb, char **entries, char *string1, char *string2, char *string3);
+int cb_add_suffix(cb_backend_instance *inst, struct berval **bvals, int apply_mod, char *returntext);
+int cb_create_default_backend_instance_config(cb_backend * cb);
+int cb_build_backend_instance_config(cb_backend_instance *inst, Slapi_Entry * conf,int apply);
+int cb_instance_delete_config_callback(Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* e2,
+ int *returncode, char *returntext, void *arg);
+int cb_instance_search_config_callback(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e,
+ int *returncode, char *returntext, void *arg);
+int cb_instance_add_config_callback(Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* e2,
+ int *returncode, char *returntext, void *arg);
+int cb_instance_modify_config_callback(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e,
+ int *returncode, char *returntext, void *arg);
+int cb_dont_allow_that(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e,
+ int *returncode, char *returntext, void *arg);
+int cb_config_search_callback(Slapi_PBlock *pb, Slapi_Entry* e1, Slapi_Entry* e2, int *returncode,
+ char *returntext, void *arg);
+int cb_config_add_callback(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, int *returncode, char *returntext, void *arg);
+int cb_config_delete_instance_callback(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, int *returncode, char *returntext, void *arg);
+int cb_config_modify_callback(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, int *returncode, char *returntext, void *arg);
+int cb_config_add_instance_callback(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, int *returncode, char *returntext, void *arg);
+int cb_delete_monitor_callback(Slapi_PBlock * pb, Slapi_Entry * e, Slapi_Entry * entryAfter, int * returnCode, char * returnText, void * arg);
+int cb_config_add_check_callback(Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* e2, int *returncode,
+ char *returntext, void *arg);
+int cb_instance_add_config_check_callback(Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* e2,
+ int *returncode, char *returntext, void *arg);
+int cb_config_modify_check_callback(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, int *returncode,
+ char *returntext, void *arg);
+
+void cb_eliminate_illegal_attributes(cb_backend_instance * inst, Slapi_Entry * e);
+void cb_release_op_connection(cb_conn_pool * pool, LDAP *ldd, int dispose);
+void cb_register_supported_control( cb_backend * cb, char *controloid, unsigned long controlops );
+void cb_unregister_all_supported_control( cb_backend * cb );
+void cb_register_supported_control( cb_backend * cb, char *controloid, unsigned long controlops );
+void cb_unregister_supported_control( cb_backend * cb, char *controloid, unsigned long controlops );
+void cb_set_acl_policy(Slapi_PBlock *pb);
+void cb_close_conn_pool(cb_conn_pool * pool);
+void cb_update_monitor_info(Slapi_PBlock * pb, cb_backend_instance * inst,int op);
+void cb_send_ldap_result(Slapi_PBlock *pb, int err, char *m,char *t, int ne, struct berval **urls );
+void cb_stale_all_connections( cb_backend_instance * be);
+int
+cb_config_add_instance_check_callback(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e,
+ int *returncode, char *returntext, void *arg);
+
+
+int chaining_back_add ( Slapi_PBlock *pb );
+int chaining_back_delete ( Slapi_PBlock *pb );
+int chaining_back_compare ( Slapi_PBlock *pb );
+int chaining_back_modify ( Slapi_PBlock *pb );
+int chaining_back_modrdn ( Slapi_PBlock *pb );
+int chaining_back_abandon ( Slapi_PBlock *pb );
+int chaining_back_entry_release ( Slapi_PBlock *pb );
+int chainingdb_next_search_entry( Slapi_PBlock *pb );
+int chainingdb_build_candidate_list ( Slapi_PBlock *pb );
+int chainingdb_start (Slapi_PBlock *pb );
+int chainingdb_bind (Slapi_PBlock *pb );
+int cb_db_size (Slapi_PBlock *pb );
+int cb_back_close (Slapi_PBlock *pb );
+int cb_back_cleanup (Slapi_PBlock *pb );
+
+long cb_atol(char *str);
+
+Slapi_Entry * cb_LDAPMessage2Entry(LDAP * ctx, LDAPMessage * msg, int attrsonly);
+char * cb_urlparse_err2string( int err );
+char * cb_get_rootdn();
+struct berval ** referrals2berval(char ** referrals);
+cb_backend_instance * cb_get_instance(Slapi_Backend * be);
+cb_backend * cb_get_backend_type();
+int cb_debug_on();
+void cb_set_debug(int on);
+int cb_ping_farm(cb_backend_instance *cb,cb_outgoing_conn * cnx,time_t end);
+void cb_update_failed_conn_cpt ( cb_backend_instance *cb ) ;
+void cb_reset_conn_cpt( cb_backend_instance *cb ) ;
+int cb_check_availability( cb_backend_instance *cb, Slapi_PBlock *pb ) ;
+
+time_t current_time();
+char* get_localhost_DNS();
+
+/* this function is called when state of a backend changes */
+void cb_be_state_change (void *handle, char *be_name, int old_be_state, int new_be_state);
+
+#endif
diff --git a/ldap/servers/plugins/chainingdb/cb_abandon.c b/ldap/servers/plugins/chainingdb/cb_abandon.c
new file mode 100644
index 00000000..ca0cfc09
--- /dev/null
+++ b/ldap/servers/plugins/chainingdb/cb_abandon.c
@@ -0,0 +1,50 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include "cb.h"
+
+/*
+ * Perform an abandon operation
+ *
+ * Returns:
+ * 0 - success
+ * <0 - fail
+ *
+ */
+
+int
+chaining_back_abandon ( Slapi_PBlock *pb )
+{
+ /*
+ * Abandon forwarded to the farm server for scoped
+ * searches only. Done in cb_search.c
+ */
+ return 0;
+}
+
+int cb_check_forward_abandon(cb_backend_instance * cb,Slapi_PBlock * pb, LDAP * ld, int msgid ) {
+
+ int rc;
+ LDAPControl ** ctrls=NULL;
+
+ if (slapi_op_abandoned( pb )) {
+
+ if ((rc=cb_forward_operation(pb)) != LDAP_SUCCESS ) {
+ return 0;
+ }
+
+ if ((rc = cb_update_controls( pb,ld,&ctrls,CB_UPDATE_CONTROLS_ISABANDON )) != LDAP_SUCCESS ) {
+ if ( NULL != ctrls)
+ ldap_controls_free(ctrls);
+ return 0;
+ }
+ rc = ldap_abandon_ext(ld, msgid, ctrls, NULL );
+ cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc));
+ if ( NULL != ctrls)
+ ldap_controls_free(ctrls);
+ return 1;
+ }
+ return 0;
+}
diff --git a/ldap/servers/plugins/chainingdb/cb_acl.c b/ldap/servers/plugins/chainingdb/cb_acl.c
new file mode 100644
index 00000000..ce0a6793
--- /dev/null
+++ b/ldap/servers/plugins/chainingdb/cb_acl.c
@@ -0,0 +1,60 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include "cb.h"
+
+/*
+** generic function to send back results
+** Turn off acl eval on front-end when needed
+*/
+
+void cb_set_acl_policy(Slapi_PBlock *pb) {
+
+ Slapi_Backend *be;
+ cb_backend_instance *cb;
+ int noacl;
+
+ slapi_pblock_get( pb, SLAPI_BACKEND, &be );
+ cb = cb_get_instance(be);
+
+ /* disable acl checking if the local_acl flag is not set
+ or if the associated backend is disabled */
+ noacl=!(cb->local_acl) || cb->associated_be_is_disabled;
+
+ if (noacl) {
+ slapi_pblock_set(pb, SLAPI_PLUGIN_DB_NO_ACL, &noacl);
+ } else {
+ /* Be very conservative about acl evaluation */
+ slapi_pblock_set(pb, SLAPI_PLUGIN_DB_NO_ACL, &noacl);
+ }
+}
+
+int cb_access_allowed(
+ Slapi_PBlock *pb,
+ Slapi_Entry *e, /* The Slapi_Entry */
+ char *attr, /* Attribute of the entry */
+ struct berval *val, /* value of attr. NOT USED */
+ int access, /* access rights */
+ char **errbuf
+ )
+
+{
+
+switch (access) {
+
+ case SLAPI_ACL_ADD:
+ case SLAPI_ACL_DELETE:
+ case SLAPI_ACL_COMPARE:
+ case SLAPI_ACL_WRITE:
+ case SLAPI_ACL_PROXY:
+
+ /* Keep in mind some entries are NOT */
+ /* available for acl evaluation */
+
+ return slapi_access_allowed(pb,e,attr,val,access);
+ default:
+ return LDAP_INSUFFICIENT_ACCESS;
+}
+}
diff --git a/ldap/servers/plugins/chainingdb/cb_add.c b/ldap/servers/plugins/chainingdb/cb_add.c
new file mode 100644
index 00000000..e0b49f7c
--- /dev/null
+++ b/ldap/servers/plugins/chainingdb/cb_add.c
@@ -0,0 +1,227 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include "cb.h"
+
+/*
+ * Perform an add operation
+ *
+ * Returns:
+ * 0 - success
+ * <0 - fail
+ *
+ */
+
+int
+chaining_back_add ( Slapi_PBlock *pb )
+{
+
+ Slapi_Backend *be;
+ Slapi_Entry *e;
+ cb_backend_instance *cb;
+ LDAPControl **serverctrls=NULL;
+ LDAPControl **ctrls=NULL;
+ int rc,parse_rc,msgid,i;
+ LDAP *ld=NULL;
+ char **referrals=NULL;
+ LDAPMod ** mods;
+ LDAPMessage * res;
+ char *dn,* matched_msg, *error_msg;
+ char *cnxerrbuf=NULL;
+ time_t endtime;
+ cb_outgoing_conn *cnx;
+
+ if ( (rc=cb_forward_operation(pb)) != LDAP_SUCCESS ) {
+ cb_send_ldap_result( pb, rc, NULL, "Remote data access disabled", 0, NULL );
+ return -1;
+ }
+
+ slapi_pblock_get( pb, SLAPI_BACKEND, &be );
+ cb = cb_get_instance(be);
+
+ /* Update monitor info */
+ cb_update_monitor_info(pb,cb,SLAPI_OPERATION_ADD);
+
+ /* Check wether the chaining BE is available or not */
+ if ( cb_check_availability( cb, pb ) == FARMSERVER_UNAVAILABLE ){
+ return -1;
+ }
+
+
+ slapi_pblock_get( pb, SLAPI_ADD_TARGET, &dn );
+ slapi_pblock_get( pb, SLAPI_ADD_ENTRY, &e );
+
+ /* Check local access controls */
+ if (cb->local_acl && !cb->associated_be_is_disabled) {
+ char * errbuf=NULL;
+ rc = cb_access_allowed (pb, e, NULL, NULL, SLAPI_ACL_ADD, &errbuf);
+ if ( rc != LDAP_SUCCESS ) {
+ cb_send_ldap_result( pb, rc, NULL, errbuf, 0, NULL );
+ slapi_ch_free((void **)&errbuf);
+ return -1;
+ }
+ }
+
+ /* Build LDAPMod from the SLapi_Entry */
+ cb_eliminate_illegal_attributes(cb,e);
+
+ if ((rc = slapi_entry2mods ((const Slapi_Entry *)e, NULL, &mods)) != LDAP_SUCCESS) {
+ cb_send_ldap_result( pb, rc,NULL,NULL, 0, NULL);
+ return -1;
+ }
+
+ /* Grab a connection handle */
+ if ((rc = cb_get_connection(cb->pool,&ld,&cnx,NULL,&cnxerrbuf)) != LDAP_SUCCESS) {
+ cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR,NULL,cnxerrbuf, 0, NULL);
+ ldap_mods_free(mods,1);
+ slapi_ch_free((void **)&cnxerrbuf);
+ /* ping the farm. If the farm is unreachable, we increment the counter */
+ cb_ping_farm(cb,NULL,0);
+
+ return -1;
+ }
+
+ /* Control management */
+ if ( (rc = cb_update_controls( pb,ld,&ctrls,CB_UPDATE_CONTROLS_ADDAUTH)) != LDAP_SUCCESS ) {
+ cb_send_ldap_result( pb, rc, NULL,NULL, 0, NULL);
+ cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc));
+ ldap_mods_free(mods,1);
+ return -1;
+ }
+
+ if ( slapi_op_abandoned( pb )) {
+ cb_release_op_connection(cb->pool,ld,0);
+ ldap_mods_free(mods,1);
+ if ( NULL != ctrls)
+ ldap_controls_free(ctrls);
+ return -1;
+ }
+
+ /* heart-beat management */
+ if (cb->max_idle_time>0)
+ endtime=current_time() + cb->max_idle_time;
+
+ /* Send LDAP operation to the remote host */
+ rc = ldap_add_ext( ld, dn, mods, ctrls, NULL, &msgid );
+
+ if ( NULL != ctrls)
+ ldap_controls_free(ctrls);
+
+ if ( rc != LDAP_SUCCESS ) {
+
+ cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL,
+ ldap_err2string(rc), 0, NULL);
+ cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc));
+ ldap_mods_free(mods,1);
+ return -1;
+ }
+
+ /*
+ * Poll the server for the results of the add operation.
+ * Check for abandoned operation regularly.
+ */
+
+ while ( 1 ) {
+
+ if (cb_check_forward_abandon(cb,pb,ld,msgid)) {
+ /* connection handle released in cb_check_forward_abandon() */
+ ldap_mods_free(mods,1);
+ return -1;
+ }
+
+ rc = ldap_result( ld, msgid, 0, &cb->abandon_timeout, &res );
+ switch ( rc ) {
+ case -1:
+ cb_send_ldap_result(pb,LDAP_OPERATIONS_ERROR, NULL,
+ ldap_err2string(rc), 0, NULL);
+ cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc));
+ ldap_mods_free(mods,1);
+ if (res)
+ ldap_msgfree(res);
+ return -1;
+ case 0:
+
+ if ((rc=cb_ping_farm(cb,cnx,endtime)) != LDAP_SUCCESS) {
+
+ /* does not respond. give up and return a*/
+ /* error to the client. */
+
+ /*cb_send_ldap_result(pb,LDAP_OPERATIONS_ERROR, NULL,
+ ldap_err2string(rc), 0, NULL);*/
+ cb_send_ldap_result(pb,LDAP_OPERATIONS_ERROR, NULL, "FARM SERVER TEMPORARY UNAVAILABLE", 0, NULL);
+ cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc));
+ ldap_mods_free(mods,1);
+ if (res)
+ ldap_msgfree(res);
+ return -1;
+ }
+#ifdef CB_YIELD
+ DS_Sleep(PR_INTERVAL_NO_WAIT);
+#endif
+ break;
+ default:
+ serverctrls=NULL;
+ matched_msg=error_msg=NULL;
+ referrals=NULL;
+
+ parse_rc = ldap_parse_result( ld, res, &rc, &matched_msg,
+ &error_msg, &referrals, &serverctrls, 1 );
+
+ if ( parse_rc != LDAP_SUCCESS ) {
+ cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL,
+ ldap_err2string(parse_rc), 0, NULL);
+ cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(parse_rc));
+ ldap_mods_free(mods,1);
+ slapi_ch_free((void **)&matched_msg);
+ slapi_ch_free((void **)&error_msg);
+ if (serverctrls)
+ ldap_controls_free(serverctrls);
+ /* jarnou: free referrals */
+ if (referrals)
+ charray_free(referrals);
+ return -1;
+ }
+
+ if ( rc != LDAP_SUCCESS ) {
+ struct berval ** refs = referrals2berval(referrals);
+ cb_send_ldap_result( pb, rc, matched_msg, error_msg, 0, refs);
+ cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc));
+ ldap_mods_free(mods,1);
+ slapi_ch_free((void **)&matched_msg);
+ slapi_ch_free((void **)&error_msg);
+ if (refs)
+ ber_bvecfree(refs);
+ if (referrals)
+ charray_free(referrals);
+ if (serverctrls)
+ ldap_controls_free(serverctrls);
+ return -1;
+ }
+
+ ldap_mods_free(mods,1 );
+ cb_release_op_connection(cb->pool,ld,0);
+
+ /* Add control response sent by the farm server */
+
+ for (i=0; serverctrls && serverctrls[i];i++)
+ slapi_pblock_set( pb, SLAPI_ADD_RESCONTROL, serverctrls[i]);
+ if (serverctrls)
+ ldap_controls_free(serverctrls);
+ /* jarnou: free matched_msg, error_msg, and referrals if necessary */
+ slapi_ch_free((void **)&matched_msg);
+ slapi_ch_free((void **)&error_msg);
+ if (referrals)
+ charray_free(referrals);
+ cb_send_ldap_result( pb, LDAP_SUCCESS, NULL, NULL, 0, NULL );
+
+ slapi_entry_free(e);
+ slapi_pblock_set( pb, SLAPI_ADD_ENTRY, NULL );
+
+ return 0;
+ }
+ }
+
+ /* Never reached */
+}
diff --git a/ldap/servers/plugins/chainingdb/cb_bind.c b/ldap/servers/plugins/chainingdb/cb_bind.c
new file mode 100644
index 00000000..495b672f
--- /dev/null
+++ b/ldap/servers/plugins/chainingdb/cb_bind.c
@@ -0,0 +1,290 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include "cb.h"
+
+static void
+cb_free_bervals( struct berval **bvs );
+
+
+static int
+cb_sasl_bind_once_s( cb_conn_pool *pool, char *dn, int method, char * mechanism,
+ struct berval *creds, LDAPControl **reqctrls,
+ char **matcheddnp, char **errmsgp, struct berval ***refurlsp,
+ LDAPControl ***resctrlsp , int * status);
+
+/*
+ * Attempt to chain a bind request off to "srvr." We return an LDAP error
+ * code that indicates whether we successfully got a response from the
+ * other server or not. If we succeed, we return LDAP_SUCCESS and *lderrnop
+ * is set to the result code from the remote server.
+ *
+ * Note that in the face of "ldap server down" or "ldap connect failed" errors
+ * we make up to "tries" attempts to bind to the remote server. Since we
+ * are only interested in recovering silently when the remote server is up
+ * but decided to close our connection, we retry without pausing between
+ * attempts.
+ */
+
+static int
+cb_sasl_bind_s(Slapi_PBlock * pb, cb_conn_pool *pool, int tries,
+ char *dn, int method,char * mechanism, struct berval *creds, LDAPControl **reqctrls,
+ char **matcheddnp, char **errmsgp, struct berval ***refurlsp,
+ LDAPControl ***resctrlsp ,int *status) {
+
+ int rc;
+
+ do {
+ /* check to see if operation has been abandoned...*/
+
+ if (LDAP_AUTH_SIMPLE!=method)
+ return LDAP_AUTH_METHOD_NOT_SUPPORTED;
+
+ if ( slapi_op_abandoned( pb )) {
+ rc = LDAP_USER_CANCELLED;
+ } else {
+ rc = cb_sasl_bind_once_s( pool, dn, method,mechanism, creds, reqctrls,
+ matcheddnp, errmsgp, refurlsp, resctrlsp ,status);
+ }
+ } while ( CB_LDAP_CONN_ERROR( rc ) && --tries > 0 );
+
+ return( rc );
+}
+
+static int
+cb_sasl_bind_once_s( cb_conn_pool *pool, char *dn, int method, char * mechanism,
+ struct berval *creds, LDAPControl **reqctrls,
+ char **matcheddnp, char **errmsgp, struct berval ***refurlsp,
+ LDAPControl ***resctrlsp , int * status) {
+
+ int rc, msgid;
+ char **referrals;
+ struct timeval timeout_copy, *timeout;
+ LDAPMessage *result=NULL;
+ LDAP *ld=NULL;
+ char *cnxerrbuf=NULL;
+ cb_outgoing_conn *cnx;
+ int version=LDAP_VERSION3;
+
+ /* Grab an LDAP connection to use for this bind. */
+
+ PR_RWLock_Rlock(pool->rwl_config_lock);
+ timeout_copy.tv_sec = pool->conn.bind_timeout.tv_sec;
+ timeout_copy.tv_usec = pool->conn.bind_timeout.tv_usec;
+ PR_RWLock_Unlock(pool->rwl_config_lock);
+
+ if (( rc = cb_get_connection( pool, &ld ,&cnx, NULL, &cnxerrbuf)) != LDAP_SUCCESS ) {
+ *errmsgp=cnxerrbuf;
+ goto release_and_return;
+ }
+
+ /* Send the bind operation (need to retry on LDAP_SERVER_DOWN) */
+
+ ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, &version );
+
+ if (( rc = ldap_sasl_bind( ld, dn, LDAP_SASL_SIMPLE, creds, reqctrls,
+ NULL, &msgid )) != LDAP_SUCCESS ) {
+ goto release_and_return;
+ }
+
+ /* XXXSD what is the exact semantics of bind_to ? it is used to get a
+ connection handle and later to bind ==> bind op may last 2*bind_to
+ from the user point of view
+ confusion comes from teh fact that bind to is used 2for 3 differnt thinks,
+ */
+
+ /*
+ * determine timeout value (how long we will wait for a response)
+ * if timeout is zero'd, we wait indefinitely.
+ */
+ if ( timeout_copy.tv_sec == 0 && timeout_copy.tv_usec == 0 ) {
+ timeout = NULL;
+ } else {
+ timeout = &timeout_copy;
+ }
+
+ /*
+ * Wait for a result.
+ */
+ rc = ldap_result( ld, msgid, 1, timeout, &result );
+
+ /*
+ * Interpret the result.
+ */
+
+ if ( rc == 0 ) { /* timeout */
+ /*
+ * Timed out waiting for a reply from the server.
+ */
+ rc = LDAP_TIMEOUT;
+ } else if ( rc < 0 ) {
+
+ /* Some other error occurred (no result received). */
+ char * matcheddnp2, * errmsgp2;
+ matcheddnp2=errmsgp2=NULL;
+
+ rc = ldap_get_lderrno( ld, &matcheddnp2, &errmsgp2 );
+
+ /* Need to allocate errmsgs */
+ if (matcheddnp2)
+ *matcheddnp=slapi_ch_strdup(matcheddnp2);
+ if (errmsgp2)
+ *errmsgp=slapi_ch_strdup(errmsgp2);
+
+ if ( LDAP_SUCCESS != rc ) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "cb_sasl_bind_once_s failed (%s)\n",ldap_err2string(rc));
+ }
+ } else {
+
+ /* Got a result from remote server -- parse it.*/
+
+ char * matcheddnp2, * errmsgp2;
+ matcheddnp2=errmsgp2=NULL;
+ *resctrlsp=NULL;
+ rc = ldap_parse_result( ld, result, status, matcheddnp, errmsgp,
+ &referrals, resctrlsp, 1 );
+ if ( referrals != NULL ) {
+ *refurlsp = referrals2berval( referrals );
+ ldap_value_free( referrals );
+ }
+ /* realloc matcheddn & errmsg because the mem alloc model */
+ /* may differ from malloc */
+ if (matcheddnp2) {
+ *matcheddnp=slapi_ch_strdup(matcheddnp2);
+ ldap_memfree(matcheddnp2);
+ }
+ if (errmsgp2) {
+ *errmsgp=slapi_ch_strdup(errmsgp2);
+ ldap_memfree(errmsgp2);
+ }
+
+ }
+
+release_and_return:
+ if ( ld != NULL ) {
+ cb_release_op_connection( pool, ld, CB_LDAP_CONN_ERROR( rc ));
+ }
+
+ return( rc );
+}
+
+int
+chainingdb_bind( Slapi_PBlock *pb ) {
+
+ int status=LDAP_SUCCESS;
+ int allocated_errmsg;
+ int rc=LDAP_SUCCESS;
+ cb_backend_instance *cb;
+ Slapi_Backend *be;
+ char *dn;
+ int method;
+ struct berval *creds, **urls;
+ char *matcheddn,*errmsg;
+ LDAPControl **reqctrls, **resctrls, **ctrls;
+ char * mechanism;
+ int freectrls=1;
+ int bind_retry;
+
+ if ( LDAP_SUCCESS != (rc = cb_forward_operation(pb) )) {
+ cb_send_ldap_result( pb, rc, NULL, "Chaining forbidden", 0, NULL );
+ return SLAPI_BIND_FAIL;
+ }
+
+ ctrls=NULL;
+ /* don't add proxy auth control. use this call to check for supported */
+ /* controls only. */
+ if ( LDAP_SUCCESS != ( rc = cb_update_controls( pb, NULL, &ctrls, 0 )) ) {
+ cb_send_ldap_result( pb, rc, NULL, NULL, 0, NULL );
+ if (ctrls)
+ ldap_controls_free(ctrls);
+ return SLAPI_BIND_FAIL;
+ }
+ if (ctrls)
+ ldap_controls_free(ctrls);
+
+ slapi_pblock_get( pb, SLAPI_BACKEND, &be );
+ slapi_pblock_get( pb, SLAPI_BIND_TARGET, &dn );
+ slapi_pblock_get( pb, SLAPI_BIND_METHOD, &method );
+ slapi_pblock_get( pb, SLAPI_BIND_SASLMECHANISM, &mechanism);
+ slapi_pblock_get( pb, SLAPI_BIND_CREDENTIALS, &creds );
+ slapi_pblock_get( pb, SLAPI_REQCONTROLS, &reqctrls );
+ cb = cb_get_instance(be);
+
+ if ( NULL == dn )
+ dn="";
+
+ /* always allow noauth simple binds */
+ if (( method == LDAP_AUTH_SIMPLE) && creds->bv_len == 0 ) {
+ return( SLAPI_BIND_ANONYMOUS );
+ }
+
+ cb_update_monitor_info(pb,cb,SLAPI_OPERATION_BIND);
+
+ matcheddn=errmsg=NULL;
+ allocated_errmsg = 0;
+ resctrls=NULL;
+ urls=NULL;
+
+ /* Check wether the chaining BE is available or not */
+ if ( cb_check_availability( cb, pb ) == FARMSERVER_UNAVAILABLE ){
+ return -1;
+ }
+
+ PR_RWLock_Rlock(cb->rwl_config_lock);
+ bind_retry=cb->bind_retry;
+ PR_RWLock_Unlock(cb->rwl_config_lock);
+
+ if ( LDAP_SUCCESS == (rc = cb_sasl_bind_s(pb, cb->bind_pool, bind_retry, dn,method,mechanism,
+ creds,reqctrls,&matcheddn,&errmsg,&urls,&resctrls, &status))) {
+ rc = status;
+ allocated_errmsg = 1;
+ } else
+ if ( LDAP_USER_CANCELLED != rc ) {
+ errmsg = ldap_err2string( rc );
+ if (rc == LDAP_TIMEOUT) {
+ cb_ping_farm(cb,NULL,NULL);
+ }
+ rc = LDAP_OPERATIONS_ERROR;
+ }
+
+ if ( rc != LDAP_USER_CANCELLED ) { /* not abandoned */
+ if ( resctrls != NULL ) {
+ slapi_pblock_set( pb, SLAPI_RESCONTROLS, resctrls );
+ freectrls=0;
+ }
+
+ if ( rc != LDAP_SUCCESS ) {
+ cb_send_ldap_result( pb, rc, matcheddn, errmsg, 0, urls );
+ }
+ }
+
+ if ( urls != NULL ) {
+ cb_free_bervals( urls );
+ }
+ if ( freectrls && ( resctrls != NULL )) {
+ ldap_controls_free( resctrls );
+ }
+ slapi_ch_free((void **)& matcheddn );
+ if ( allocated_errmsg && errmsg != NULL ) {
+ slapi_ch_free((void **)& errmsg );
+ }
+
+ return ((rc == LDAP_SUCCESS ) ? SLAPI_BIND_SUCCESS : SLAPI_BIND_FAIL );
+}
+
+static void
+cb_free_bervals( struct berval **bvs )
+{
+ int i;
+
+ if ( bvs != NULL ) {
+ for ( i = 0; bvs[ i ] != NULL; ++i ) {
+ slapi_ch_free( (void **)&bvs[ i ] );
+ }
+ }
+ slapi_ch_free( (void **)&bvs );
+}
+
diff --git a/ldap/servers/plugins/chainingdb/cb_cleanup.c b/ldap/servers/plugins/chainingdb/cb_cleanup.c
new file mode 100644
index 00000000..b034b0a1
--- /dev/null
+++ b/ldap/servers/plugins/chainingdb/cb_cleanup.c
@@ -0,0 +1,22 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include "cb.h"
+
+/*
+** cLeanup a chaining backend instance
+*/
+
+int cb_back_cleanup( Slapi_PBlock *pb )
+{
+
+ /*
+ ** Connections have been closed in cb_back_close()
+ ** For now, don't do more
+ */
+
+ return 0;
+}
+
diff --git a/ldap/servers/plugins/chainingdb/cb_close.c b/ldap/servers/plugins/chainingdb/cb_close.c
new file mode 100644
index 00000000..3ef52e32
--- /dev/null
+++ b/ldap/servers/plugins/chainingdb/cb_close.c
@@ -0,0 +1,67 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include "cb.h"
+
+/*
+** Close a chaining backend instance
+** Should be followed by a cleanup
+*/
+
+int cb_back_close( Slapi_PBlock *pb )
+{
+ Slapi_Backend * be;
+ cb_backend_instance * inst;
+ int rc;
+
+ slapi_pblock_get( pb, SLAPI_BACKEND, &be );
+ if (be == NULL) {
+
+ cb_backend * cb = cb_get_backend_type();
+ CB_ASSERT(cb!=NULL);
+
+ slapi_config_remove_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_POSTOP, cb->configDN, LDAP_SCOPE_BASE,
+ "(objectclass=*)",cb_config_modify_callback);
+ slapi_config_remove_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_PREOP, cb->configDN, LDAP_SCOPE_BASE,
+ "(objectclass=*)",cb_config_modify_check_callback);
+
+ slapi_config_remove_callback(SLAPI_OPERATION_ADD, DSE_FLAG_POSTOP, cb->configDN, LDAP_SCOPE_BASE,
+ "(objectclass=*)",cb_config_add_callback);
+ slapi_config_remove_callback(SLAPI_OPERATION_ADD, DSE_FLAG_PREOP, cb->configDN, LDAP_SCOPE_BASE,
+ "(objectclass=*)",cb_config_add_check_callback);
+
+ slapi_config_remove_callback(SLAPI_OPERATION_SEARCH, DSE_FLAG_PREOP, cb->configDN, LDAP_SCOPE_BASE,
+ "(objectclass=*)",cb_config_search_callback);
+
+ slapi_config_remove_callback(SLAPI_OPERATION_ADD, DSE_FLAG_POSTOP, cb->pluginDN,
+ LDAP_SCOPE_SUBTREE, CB_CONFIG_INSTANCE_FILTER, cb_config_add_instance_callback);
+
+ return 0;
+ }
+
+ /* XXXSD: temp fix . Sometimes, this functions */
+ /* gets called with a ldbm backend instance... */
+
+ {
+ const char * betype = slapi_be_gettype(be);
+ if (!betype || strcasecmp(betype,CB_CHAINING_BACKEND_TYPE)) {
+
+ slapi_log_error( SLAPI_LOG_FATAL, CB_PLUGIN_SUBSYSTEM,
+ "Wrong database type.\n");
+ return 0;
+ }
+ }
+
+ inst = cb_get_instance(be);
+ CB_ASSERT( inst!=NULL );
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,"Stopping chaining database instance %s\n",
+ inst->configDn);
+ /* emulate a backend instance deletion */
+ /* to clean up everything */
+ cb_instance_delete_config_callback(NULL, NULL,NULL, &rc, NULL, inst);
+
+ return 0;
+}
diff --git a/ldap/servers/plugins/chainingdb/cb_compare.c b/ldap/servers/plugins/chainingdb/cb_compare.c
new file mode 100644
index 00000000..ebce1645
--- /dev/null
+++ b/ldap/servers/plugins/chainingdb/cb_compare.c
@@ -0,0 +1,217 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include "cb.h"
+
+/*
+ * Perform a compare operation
+ *
+ * Returns:
+ * 0 - success
+ * <0 - fail
+ *
+ */
+
+int
+chaining_back_compare ( Slapi_PBlock *pb )
+{
+
+ Slapi_Backend * be;
+ cb_backend_instance *cb=NULL;
+ struct berval *bval=NULL;
+ LDAPControl **ctrls, **serverctrls;
+ int rc,parse_rc,msgid,i,checkacl;
+ LDAP *ld=NULL;
+ char **referrals=NULL;
+ LDAPMessage * res;
+ char *type,*dn,* matched_msg, *error_msg;
+ char *cnxerrbuf=NULL;
+ time_t endtime;
+ cb_outgoing_conn *cnx;
+
+ if ( LDAP_SUCCESS != (rc=cb_forward_operation(pb) )) {
+ cb_send_ldap_result( pb, rc, NULL, "Chaining forbidden", 0, NULL );
+ return -1;
+ }
+
+ slapi_pblock_get( pb, SLAPI_BACKEND, &be );
+ cb = cb_get_instance(be);
+
+ cb_update_monitor_info(pb,cb,SLAPI_OPERATION_COMPARE);
+
+ /* Check wether the chaining BE is available or not */
+ if ( cb_check_availability( cb, pb ) == FARMSERVER_UNAVAILABLE ){
+ return -1;
+ }
+
+ slapi_pblock_get( pb, SLAPI_COMPARE_TARGET, &dn );
+ slapi_pblock_get( pb, SLAPI_COMPARE_TYPE, &type );
+ slapi_pblock_get( pb, SLAPI_COMPARE_VALUE, &bval );
+
+ /*
+ * Check local acls
+ * No need to lock the config to access cb->local_acl
+ */
+
+ checkacl=cb->local_acl && !cb->associated_be_is_disabled;
+
+ if (checkacl) {
+ char * errbuf=NULL;
+ Slapi_Entry *te = slapi_entry_alloc();
+ slapi_entry_set_dn(te,slapi_ch_strdup(dn));
+ rc = cb_access_allowed (pb, te, type, bval, SLAPI_ACL_COMPARE,&errbuf);
+ slapi_entry_free(te);
+
+ if ( rc != LDAP_SUCCESS ) {
+ cb_send_ldap_result( pb, rc, NULL, errbuf, 0, NULL );
+ slapi_ch_free((void **) &errbuf);
+ return 1;
+ }
+ }
+
+ /*
+ * Grab a connection handle
+ */
+
+ if ((rc = cb_get_connection(cb->pool,&ld,&cnx,NULL,&cnxerrbuf)) != LDAP_SUCCESS) {
+ cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL, cnxerrbuf, 0, NULL);
+ slapi_ch_free((void **)&cnxerrbuf);
+ /* ping the farm. If the farm is unreachable, we increment the counter */
+ cb_ping_farm(cb,NULL,0);
+ return 1;
+ }
+
+ /*
+ * Control management
+ */
+
+ if ( (rc = cb_update_controls( pb,ld,&ctrls,CB_UPDATE_CONTROLS_ADDAUTH )) != LDAP_SUCCESS ) {
+ cb_send_ldap_result( pb, rc, NULL,NULL, 0, NULL);
+ cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc));
+ return 1;
+ }
+
+ if ( slapi_op_abandoned( pb )) {
+ cb_release_op_connection(cb->pool,ld,0);
+ if ( NULL != ctrls)
+ ldap_controls_free(ctrls);
+ return -1;
+ }
+
+ /* heart-beat management */
+ if (cb->max_idle_time>0)
+ endtime=current_time() + cb->max_idle_time;
+
+ /*
+ * Send LDAP operation to the remote host
+ */
+
+ rc = ldap_compare_ext( ld, dn, type, bval, ctrls, NULL, &msgid );
+ if ( NULL != ctrls)
+ ldap_controls_free(ctrls);
+
+ if ( rc != LDAP_SUCCESS ) {
+ cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL,
+ ldap_err2string(rc), 0, NULL);
+ cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc));
+ return 1;
+ }
+
+ while ( 1 ) {
+
+ if (cb_check_forward_abandon(cb,pb,ld,msgid)) {
+ return -1;
+ }
+
+ /* No need to lock the config to access cb->abandon_timeout */
+ rc = ldap_result( ld, msgid, 0, &cb->abandon_timeout, &res );
+ switch ( rc ) {
+ case -1:
+ cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL,
+ ldap_err2string(rc), 0, NULL);
+ cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc));
+ if (res)
+ ldap_msgfree(res);
+ return 1;
+ case 0:
+ if ((rc=cb_ping_farm(cb,cnx,endtime)) != LDAP_SUCCESS) {
+
+ /* does not respond. give up and return a*/
+ /* error to the client. */
+
+ /*cb_send_ldap_result(pb,LDAP_OPERATIONS_ERROR, NULL,
+ ldap_err2string(rc), 0, NULL);*/
+ cb_send_ldap_result(pb,LDAP_OPERATIONS_ERROR, NULL, "FARM SERVER TEMPORARY UNAVAILABLE", 0, NULL);
+ cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc));
+ if (res)
+ ldap_msgfree(res);
+ return 1;
+ }
+#ifdef CB_YIELD
+ DS_Sleep(PR_INTERVAL_NO_WAIT);
+#endif
+ break;
+ default:
+ matched_msg=error_msg=NULL;
+ parse_rc = ldap_parse_result( ld, res, &rc, &matched_msg,
+ &error_msg, &referrals, &serverctrls, 1 );
+ if ( parse_rc != LDAP_SUCCESS ) {
+
+ cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL,
+ ldap_err2string(parse_rc), 0, NULL);
+ cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(parse_rc));
+ slapi_ch_free((void **)&matched_msg);
+ slapi_ch_free((void **)&error_msg);
+ if (serverctrls)
+ ldap_controls_free(serverctrls);
+ /* jarnou: free referrals */
+ if (referrals)
+ charray_free(referrals);
+ return 1;
+ }
+
+ switch ( rc ) {
+
+ case LDAP_COMPARE_TRUE:
+ case LDAP_COMPARE_FALSE:
+ break;
+ default: {
+ struct berval ** refs = referrals2berval(referrals);
+
+ cb_send_ldap_result( pb, rc, matched_msg, error_msg, 0, refs);
+ cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc));
+ slapi_ch_free((void **)&matched_msg);
+ slapi_ch_free((void **)&error_msg);
+ if (refs)
+ ber_bvecfree(refs);
+ if (referrals)
+ charray_free(referrals);
+ if (serverctrls)
+ ldap_controls_free(serverctrls);
+ return 1;
+ }
+ }
+
+ /* Add control response sent by the farm server */
+
+ for (i=0; serverctrls && serverctrls[i];i++)
+ slapi_pblock_set( pb, SLAPI_ADD_RESCONTROL, serverctrls[i]);
+ if (serverctrls)
+ ldap_controls_free(serverctrls);
+ /* jarnou: free matched_msg, error_msg, and referrals if necessary */
+ slapi_ch_free((void **)&matched_msg);
+ slapi_ch_free((void **)&error_msg);
+ if (referrals)
+ charray_free(referrals);
+
+ cb_send_ldap_result( pb, rc , NULL, NULL, 0, NULL );
+ cb_release_op_connection(cb->pool,ld,0);
+ return 0;
+ }
+ }
+
+ /* Never reached */
+ /* return 0; */
+}
diff --git a/ldap/servers/plugins/chainingdb/cb_config.c b/ldap/servers/plugins/chainingdb/cb_config.c
new file mode 100644
index 00000000..13dd7b22
--- /dev/null
+++ b/ldap/servers/plugins/chainingdb/cb_config.c
@@ -0,0 +1,600 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include "cb.h"
+#include <errno.h>
+
+/* Forward declarations */
+
+static int cb_parse_config_entry(cb_backend * cb, Slapi_Entry *e);
+
+/* body starts here */
+
+/* Used to add an array of entries, like the one above and
+** cb_instance_skeleton_entries to the dse.
+** Returns 0 on success.
+*/
+
+
+
+int cb_config_add_dse_entries(cb_backend *cb, char **entries, char *string1, char *string2, char *string3)
+{
+ int x;
+ Slapi_Entry *e;
+ Slapi_PBlock *util_pb = NULL;
+ int res, rc = 0;
+ char entry_string[CB_BUFSIZE];
+
+ for(x = 0; strlen(entries[x]) > 0; x++) {
+ util_pb = slapi_pblock_new();
+ sprintf(entry_string, entries[x], string1, string2, string3);
+ e = slapi_str2entry(entry_string, 0);
+ slapi_add_entry_internal_set_pb(util_pb, e, NULL, cb->identity, 0);
+ slapi_add_internal_pb(util_pb);
+ slapi_pblock_get(util_pb, SLAPI_PLUGIN_INTOP_RESULT, &res);
+ if ( LDAP_SUCCESS != res && LDAP_ALREADY_EXISTS != res ) {
+ char ebuf[ BUFSIZ ];
+ slapi_log_error(SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "Unable to add config entry (%s) to the DSE: %s\n",
+ escape_string(slapi_entry_get_dn(e), ebuf),
+ ldap_err2string(res));
+ rc = res;
+ slapi_pblock_destroy(util_pb);
+ break;
+ }
+ slapi_pblock_destroy(util_pb);
+ }
+ return rc;
+}
+
+/*
+** Try to read the entry cn=config,cn=chaining database,cn=plugins,cn=config
+** If the entry is there, then process the configuration information it stores.
+** If it is missing, create it with default configuration.
+** The default configuration is taken from the default entry if it exists
+*/
+
+int cb_config_load_dse_info(Slapi_PBlock * pb) {
+
+ Slapi_PBlock *search_pb,*default_pb;
+ Slapi_Entry **entries = NULL;
+ Slapi_Entry *configEntry=NULL;
+ int res,default_res,i;
+ char defaultDn[CB_BUFSIZE];
+ cb_backend *cb;
+
+ slapi_pblock_get( pb, SLAPI_PLUGIN_PRIVATE, &cb );
+
+ /* Get global configuration entry */
+ search_pb = slapi_pblock_new();
+ slapi_search_internal_set_pb(search_pb, cb->configDN, LDAP_SCOPE_BASE,
+ "objectclass=*", NULL, 0, NULL, NULL, cb->identity, 0);
+ slapi_search_internal_pb (search_pb);
+ slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_RESULT, &res);
+
+ if ( LDAP_SUCCESS == res ) {
+ slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries);
+ if (NULL == entries || entries[0] == NULL) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "Error accessing entry <%s>\n",cb->configDN);
+ slapi_free_search_results_internal(search_pb);
+ slapi_pblock_destroy(search_pb);
+ return 1;
+ }
+ configEntry=entries[0];
+ } else
+ if ( LDAP_NO_SUCH_OBJECT == res ) {
+ /* Don't do anything. The default conf is used */
+ configEntry=NULL;
+ } else {
+ slapi_free_search_results_internal(search_pb);
+ slapi_pblock_destroy(search_pb);
+ slapi_log_error(SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "Error accessing entry <%s> (%s)\n",cb->configDN,ldap_err2string(res));
+ return 1;
+ }
+
+ /* Parse the configuration entry */
+ /* Default config if configEntry is NULL*/
+
+ cb_parse_config_entry(cb, configEntry);
+ slapi_free_search_results_internal(search_pb);
+ slapi_pblock_destroy(search_pb);
+
+ /*
+ ** Parse the chaining backend instances
+ ** Immediate subordinates of cn=<plugin name>,cn=plugins,cn=config
+ */
+
+ search_pb = slapi_pblock_new();
+
+ slapi_search_internal_set_pb(search_pb, cb->pluginDN, LDAP_SCOPE_ONELEVEL,
+ CB_CONFIG_INSTANCE_FILTER,NULL,0,NULL,NULL,cb->identity, 0);
+ slapi_search_internal_pb (search_pb);
+ slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_RESULT, &res);
+ if (res != LDAP_SUCCESS) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "Error accessing the config DSE (%s)\n",ldap_err2string(res));
+ slapi_free_search_results_internal(search_pb);
+ slapi_pblock_destroy(search_pb);
+ return 1;
+ }
+
+ /* Get the default instance value entry if it exists */
+ /* else create it */
+
+ sprintf(defaultDn,"cn=default instance config,%s",cb->pluginDN);
+
+ default_pb = slapi_pblock_new();
+ slapi_search_internal_set_pb(default_pb, defaultDn, LDAP_SCOPE_BASE,
+ "objectclass=*", NULL, 0, NULL, NULL, cb->identity, 0);
+ slapi_search_internal_pb (default_pb);
+ slapi_pblock_get(default_pb, SLAPI_PLUGIN_INTOP_RESULT, &default_res);
+ if (LDAP_SUCCESS != default_res) {
+ cb_create_default_backend_instance_config(cb);
+ }
+
+ slapi_free_search_results_internal(default_pb);
+ slapi_pblock_destroy(default_pb);
+
+ slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries);
+ for (i=0; entries && entries[i]; i++) {
+ int retcode;
+ char * aDn=slapi_entry_get_dn(entries[i]);
+ slapi_dn_normalize(aDn);
+
+ cb_instance_add_config_callback(pb,entries[i],NULL,&retcode,NULL,cb);
+ }
+
+ slapi_free_search_results_internal(search_pb);
+ slapi_pblock_destroy(search_pb);
+
+
+ /* Add callbacks */
+ slapi_config_register_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_PREOP, cb->configDN,
+ LDAP_SCOPE_BASE, "(objectclass=*)",cb_config_modify_check_callback, (void *) cb);
+ slapi_config_register_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_POSTOP, cb->configDN,
+ LDAP_SCOPE_BASE, "(objectclass=*)",cb_config_modify_callback, (void *) cb);
+
+ slapi_config_register_callback(SLAPI_OPERATION_ADD, DSE_FLAG_PREOP, cb->configDN,
+ LDAP_SCOPE_BASE, "(objectclass=*)",cb_config_add_check_callback, (void *) cb);
+ slapi_config_register_callback(SLAPI_OPERATION_ADD, DSE_FLAG_POSTOP, cb->configDN,
+ LDAP_SCOPE_BASE, "(objectclass=*)",cb_config_add_callback, (void *) cb);
+
+ slapi_config_register_callback(SLAPI_OPERATION_SEARCH, DSE_FLAG_PREOP, cb->configDN,
+ LDAP_SCOPE_BASE, "(objectclass=*)",cb_config_search_callback, (void *) cb);
+
+ /* instance creation */
+ slapi_config_register_callback(SLAPI_OPERATION_ADD, DSE_FLAG_PREOP, cb->pluginDN,
+ LDAP_SCOPE_SUBTREE, CB_CONFIG_INSTANCE_FILTER, cb_config_add_instance_check_callback, (void *) cb);
+
+ slapi_config_register_callback(SLAPI_OPERATION_ADD, DSE_FLAG_POSTOP, cb->pluginDN,
+ LDAP_SCOPE_SUBTREE, CB_CONFIG_INSTANCE_FILTER, cb_config_add_instance_callback, (void *) cb);
+
+ return 0;
+}
+
+/* Check validity of the modification */
+
+int cb_config_add_check_callback(Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* e2, int *returncode,
+ char *returntext, void *arg)
+{
+ Slapi_Attr *attr = NULL;
+ Slapi_Value *sval;
+ struct berval * bval;
+ int i;
+ cb_backend *cb = (cb_backend *) arg;
+
+ CB_ASSERT (cb!=NULL);
+
+ for (slapi_entry_first_attr(e, &attr); attr; slapi_entry_next_attr(e, attr, &attr)) {
+ char * attr_name=NULL;
+ slapi_attr_get_type(attr, &attr_name);
+
+ if ( !strcasecmp ( attr_name, CB_CONFIG_GLOBAL_FORWARD_CTRLS )) {
+ /* First, parse the values to make sure they are valid */
+ i = slapi_attr_first_value(attr, &sval);
+ while (i != -1 ) {
+ bval = (struct berval *) slapi_value_get_berval(sval);
+ if (!cb_is_control_forwardable(cb,bval->bv_val)) {
+ slapi_log_error(SLAPI_LOG_PLUGIN,CB_PLUGIN_SUBSYSTEM,
+ "control %s can't be forwarded.\n",bval->bv_val);
+ *returncode=LDAP_CONSTRAINT_VIOLATION;
+ return SLAPI_DSE_CALLBACK_ERROR;
+ }
+ i = slapi_attr_next_value(attr, i, &sval);
+ }
+ }
+ }
+ *returncode=LDAP_SUCCESS;
+ return SLAPI_DSE_CALLBACK_OK;
+}
+
+/*
+** Global config is beeing added
+** Take the new values into account
+*/
+
+int
+cb_config_add_callback(Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* e2, int *returncode,
+ char *returntext, void *arg)
+{
+ Slapi_Attr *attr = NULL;
+ Slapi_Value *sval;
+ struct berval * bval;
+ int i;
+ cb_backend *cb = (cb_backend *) arg;
+
+ CB_ASSERT (cb!=NULL);
+
+ for (slapi_entry_first_attr(e, &attr); attr; slapi_entry_next_attr(e, attr, &attr)) {
+ char * attr_name=NULL;
+ slapi_attr_get_type(attr, &attr_name);
+
+ if ( !strcasecmp ( attr_name, CB_CONFIG_GLOBAL_FORWARD_CTRLS )) {
+ /* First, parse the values to make sure they are valid */
+ i = slapi_attr_first_value(attr, &sval);
+ while (i != -1 ) {
+ bval = (struct berval *) slapi_value_get_berval(sval);
+ if (!cb_is_control_forwardable(cb,bval->bv_val)) {
+ slapi_log_error(SLAPI_LOG_PLUGIN,CB_PLUGIN_SUBSYSTEM,
+ "control %s can't be forwarded.\n",bval->bv_val);
+ *returncode=LDAP_CONSTRAINT_VIOLATION;
+ return SLAPI_DSE_CALLBACK_ERROR;
+ }
+ i = slapi_attr_next_value(attr, i, &sval);
+ }
+ /* second pass. apply changes */
+ cb_unregister_all_supported_control(cb);
+ i = slapi_attr_first_value(attr, &sval);
+ while (i != -1 ) {
+ bval = (struct berval *) slapi_value_get_berval(sval);
+ cb_register_supported_control(cb,bval->bv_val,0);
+ i = slapi_attr_next_value(attr, i, &sval);
+ }
+ }
+ }
+ *returncode=LDAP_SUCCESS;
+ return SLAPI_DSE_CALLBACK_OK;
+}
+
+int
+cb_config_search_callback(Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* e2, int *returncode,
+ char *returntext, void *arg) {
+
+ cb_backend *cb = (cb_backend *) arg;
+ struct berval val;
+ struct berval *vals[2];
+ int i = 0;
+
+ CB_ASSERT (cb!=NULL);
+
+ vals[0] = &val;
+ vals[1] = NULL;
+
+ /* naming attribute */
+ val.bv_val = "config";
+ val.bv_len = strlen( val.bv_val );
+ slapi_entry_attr_replace( e, "cn", (struct berval **)vals );
+
+ /* objectclass attribute */
+ val.bv_val = "top";
+ val.bv_len = strlen( val.bv_val );
+ slapi_entry_attr_replace( e, "objectclass", (struct berval **)vals );
+ val.bv_val = CB_CONFIG_EXTENSIBLEOCL;
+ val.bv_len = strlen( val.bv_val );
+ slapi_entry_attr_merge( e, "objectclass", (struct berval **)vals );
+
+ /* other attributes */
+
+ PR_RWLock_Rlock(cb->config.rwl_config_lock);
+
+ for (i=0; cb->config.forward_ctrls && cb->config.forward_ctrls[i] ; i++) {
+ val.bv_val=cb->config.forward_ctrls[i];
+ val.bv_len = strlen( val.bv_val );
+ if (i==0)
+ slapi_entry_attr_replace( e, CB_CONFIG_GLOBAL_FORWARD_CTRLS, (struct berval **)vals );
+ else
+ slapi_entry_attr_merge( e, CB_CONFIG_GLOBAL_FORWARD_CTRLS, (struct berval **)vals );
+ }
+
+ for (i=0;cb->config.chaining_components && cb->config.chaining_components[i];i++) {
+ val.bv_val=cb->config.chaining_components[i];
+ val.bv_len = strlen( val.bv_val );
+ if (i==0)
+ slapi_entry_attr_replace( e, CB_CONFIG_GLOBAL_CHAINING_COMPONENTS,
+ (struct berval **)vals );
+ else
+ slapi_entry_attr_merge( e, CB_CONFIG_GLOBAL_CHAINING_COMPONENTS,
+ (struct berval **)vals );
+ }
+
+ for (i=0; cb->config.chainable_components && cb->config.chainable_components[i]; i++) {
+ val.bv_val=cb->config.chainable_components[i];
+ val.bv_len = strlen( val.bv_val );
+ if (i==0)
+ slapi_entry_attr_replace( e, CB_CONFIG_GLOBAL_CHAINABLE_COMPONENTS,
+ (struct berval **)vals );
+ else
+ slapi_entry_attr_merge( e, CB_CONFIG_GLOBAL_CHAINABLE_COMPONENTS,
+ (struct berval **)vals );
+ }
+
+
+ PR_RWLock_Unlock(cb->config.rwl_config_lock);
+
+ *returncode = LDAP_SUCCESS;
+ return SLAPI_DSE_CALLBACK_OK;
+}
+
+/* Check validity of the modification */
+
+int
+cb_config_modify_check_callback(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, int *returncode,
+ char *returntext, void *arg)
+{
+ LDAPMod **mods;
+ char *attr_name;
+ int i,j;
+ cb_backend *cb = (cb_backend *) arg;
+
+ CB_ASSERT (cb!=NULL);
+
+ slapi_pblock_get( pb, SLAPI_MODIFY_MODS, &mods );
+
+ for (i = 0; mods[i] ; i++) {
+ attr_name = mods[i]->mod_type;
+
+ if ( !strcasecmp ( attr_name, CB_CONFIG_GLOBAL_FORWARD_CTRLS )) {
+ char * config_attr_value;
+ for (j = 0; mods[i]->mod_bvalues && mods[i]->mod_bvalues[j]; j++) {
+ config_attr_value = (char *) mods[i]->mod_bvalues[j]->bv_val;
+ if (!cb_is_control_forwardable(cb,config_attr_value)) {
+ slapi_log_error(SLAPI_LOG_PLUGIN,CB_PLUGIN_SUBSYSTEM,
+ "control %s can't be forwarded.\n",config_attr_value);
+ *returncode=LDAP_CONSTRAINT_VIOLATION;
+ return SLAPI_DSE_CALLBACK_ERROR;
+ }
+ }
+ }
+ }
+ *returncode=LDAP_SUCCESS;
+ return SLAPI_DSE_CALLBACK_OK;
+}
+
+int
+cb_config_modify_callback(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, int *returncode,
+ char *returntext, void *arg)
+{
+ LDAPMod **mods;
+ char *attr_name;
+ int i,j;
+ cb_backend *cb = (cb_backend *) arg;
+
+ CB_ASSERT (cb!=NULL);
+
+ slapi_pblock_get( pb, SLAPI_MODIFY_MODS, &mods );
+
+ for (i = 0; mods[i] ; i++) {
+ attr_name = mods[i]->mod_type;
+
+ if ( !strcasecmp ( attr_name, CB_CONFIG_GLOBAL_FORWARD_CTRLS )) {
+ char * config_attr_value;
+ int done=0;
+ for (j = 0; mods[i]->mod_bvalues && mods[i]->mod_bvalues[j]; j++) {
+ config_attr_value = (char *) mods[i]->mod_bvalues[j]->bv_val;
+ if (!cb_is_control_forwardable(cb,config_attr_value)) {
+ slapi_log_error(SLAPI_LOG_PLUGIN,CB_PLUGIN_SUBSYSTEM,
+ "control %s can't be forwarded.\n",config_attr_value);
+ *returncode=LDAP_CONSTRAINT_VIOLATION;
+ return SLAPI_DSE_CALLBACK_ERROR;
+ }
+
+ if ( mods[i]->mod_op & LDAP_MOD_REPLACE) {
+ if (!done) {
+ cb_unregister_all_supported_control(cb);
+ done=1;
+ }
+ cb_register_supported_control(cb,config_attr_value,0);
+ } else
+ if ( (mods[i]->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_ADD) {
+ cb_register_supported_control(cb,config_attr_value,0);
+ } else
+ if ( (mods[i]->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_DELETE) {
+ cb_unregister_supported_control(cb,config_attr_value,0);
+ }
+ }
+ if (NULL == mods[i]->mod_bvalues)
+ cb_unregister_all_supported_control(cb);
+ } else
+ if ( !strcasecmp ( attr_name, CB_CONFIG_GLOBAL_DEBUG )) {
+ /* assume single-valued */
+ if (mods[i]->mod_op & LDAP_MOD_DELETE)
+ cb_set_debug(0);
+ else if ((mods[i]->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_ADD)
+ cb_set_debug(1);
+ } else
+ if ( !strcasecmp ( attr_name, CB_CONFIG_GLOBAL_CHAINING_COMPONENTS )) {
+ char * config_attr_value;
+ int done=0;
+
+ PR_RWLock_Wlock(cb->config.rwl_config_lock);
+
+ for (j = 0; mods[i]->mod_bvalues && mods[i]->mod_bvalues[j]; j++) {
+ config_attr_value = (char *) mods[i]->mod_bvalues[j]->bv_val;
+ if ( mods[i]->mod_op & LDAP_MOD_REPLACE) {
+ if (!done) {
+ charray_free(cb->config.chaining_components);
+ cb->config.chaining_components=NULL;
+ done=1;
+ }
+ /* XXXSD assume dn. Normalize it */
+ charray_add(&cb->config.chaining_components,
+ slapi_dn_normalize(slapi_ch_strdup(config_attr_value)));
+ } else
+ if ( (mods[i]->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_ADD) {
+ charray_add(&cb->config.chaining_components,
+ slapi_dn_normalize(slapi_ch_strdup(config_attr_value)));
+ } else
+ if ( (mods[i]->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_DELETE) {
+ charray_remove(cb->config.chaining_components,
+ slapi_dn_normalize(slapi_ch_strdup(config_attr_value)));
+ }
+ }
+ if (NULL == mods[i]->mod_bvalues) {
+ charray_free(cb->config.chaining_components);
+ cb->config.chaining_components=NULL;
+ }
+
+ PR_RWLock_Unlock(cb->config.rwl_config_lock);
+ } else
+ if ( !strcasecmp ( attr_name, CB_CONFIG_GLOBAL_CHAINABLE_COMPONENTS )) {
+ char * config_attr_value;
+ int done=0;
+
+ PR_RWLock_Wlock(cb->config.rwl_config_lock);
+
+ for (j = 0; mods[i]->mod_bvalues && mods[i]->mod_bvalues[j]; j++) {
+ config_attr_value = (char *) mods[i]->mod_bvalues[j]->bv_val;
+ if ( mods[i]->mod_op & LDAP_MOD_REPLACE) {
+ if (!done) {
+ charray_free(cb->config.chainable_components);
+ cb->config.chainable_components=NULL;
+ done=1;
+ }
+ charray_add(&cb->config.chainable_components,
+ slapi_dn_normalize(slapi_ch_strdup(config_attr_value)
+));
+ } else
+ if ( (mods[i]->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_ADD) {
+ charray_add(&cb->config.chainable_components,
+ slapi_dn_normalize(slapi_ch_strdup(config_attr_value)
+));
+ } else
+ if ( (mods[i]->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_DELETE) {
+ charray_remove(cb->config.chainable_components,
+ slapi_dn_normalize(slapi_ch_strdup(config_attr_value)
+));
+ }
+ }
+ if (NULL == mods[i]->mod_bvalues) {
+ charray_free(cb->config.chainable_components);
+ cb->config.chainable_components=NULL;
+ }
+
+ PR_RWLock_Unlock(cb->config.rwl_config_lock);
+ }
+
+
+ }
+ *returncode=LDAP_SUCCESS;
+ return SLAPI_DSE_CALLBACK_OK;
+}
+
+/*
+** Creation of a new backend instance
+*/
+
+int
+cb_config_add_instance_callback(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, int *returncode,
+ char *returntext, void *arg)
+{
+ cb_backend *cb=(cb_backend *)arg;
+ CB_ASSERT(cb!=NULL);
+ cb_instance_add_config_callback(pb,entryBefore,NULL,returncode,returntext,cb);
+ return SLAPI_DSE_CALLBACK_OK;
+}
+
+int
+cb_config_add_instance_check_callback(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, int *returncode,
+ char *returntext, void *arg)
+{
+ cb_backend *cb=(cb_backend *)arg;
+ CB_ASSERT(cb!=NULL);
+ return cb_instance_add_config_check_callback(pb,entryBefore,NULL,returncode,returntext,cb);
+}
+
+/*
+** Parse the global chaining backend configuration
+*/
+
+static int cb_parse_config_entry(cb_backend * cb, Slapi_Entry *e)
+{
+ Slapi_Attr *attr = NULL;
+ Slapi_Value *sval;
+ struct berval *bval;
+ int i;
+
+ if (e == NULL)
+ return LDAP_SUCCESS;
+
+ cb_set_debug(0);
+
+ for (slapi_entry_first_attr(e, &attr); attr; slapi_entry_next_attr(e, attr, &attr)) {
+ char * attr_name=NULL;
+ slapi_attr_get_type(attr, &attr_name);
+
+ if ( !strcasecmp ( attr_name, CB_CONFIG_GLOBAL_FORWARD_CTRLS )) {
+ i = slapi_attr_first_value(attr, &sval);
+
+ PR_RWLock_Wlock(cb->config.rwl_config_lock);
+ if (cb->config.forward_ctrls) {
+ charray_free(cb->config.forward_ctrls);
+ cb->config.forward_ctrls=NULL;
+ }
+ PR_RWLock_Unlock(cb->config.rwl_config_lock);
+
+ while (i != -1 ) {
+ bval = (struct berval *) slapi_value_get_berval(sval);
+ /* For now, don't support operation type */
+ cb_register_supported_control(cb,bval->bv_val,
+ SLAPI_OPERATION_SEARCH | SLAPI_OPERATION_COMPARE |
+ SLAPI_OPERATION_ADD | SLAPI_OPERATION_DELETE |
+ SLAPI_OPERATION_MODIFY | SLAPI_OPERATION_MODDN);
+ i = slapi_attr_next_value(attr, i, &sval);
+ }
+ } else
+ if ( !strcasecmp ( attr_name, CB_CONFIG_GLOBAL_CHAINING_COMPONENTS )) {
+ i = slapi_attr_first_value(attr, &sval);
+ PR_RWLock_Wlock(cb->config.rwl_config_lock);
+ if (cb->config.chaining_components) {
+ charray_free(cb->config.chaining_components);
+ cb->config.chaining_components=NULL;
+ }
+ while (i != -1 ) {
+ bval = (struct berval *) slapi_value_get_berval(sval);
+ /* XXXSD assume dn. Normalize it */
+ charray_add( &cb->config.chaining_components,
+ slapi_dn_normalize(slapi_ch_strdup(bval->bv_val)));
+ i = slapi_attr_next_value(attr, i, &sval);
+ }
+ PR_RWLock_Unlock(cb->config.rwl_config_lock);
+ } else
+ if ( !strcasecmp ( attr_name, CB_CONFIG_GLOBAL_CHAINABLE_COMPONENTS )) {
+ i = slapi_attr_first_value(attr, &sval);
+ PR_RWLock_Wlock(cb->config.rwl_config_lock);
+ if (cb->config.chainable_components) {
+ charray_free(cb->config.chainable_components);
+ cb->config.chainable_components=NULL;
+ }
+ while (i != -1 ) {
+ bval = (struct berval *) slapi_value_get_berval(sval);
+ charray_add( &cb->config.chainable_components,
+ slapi_dn_normalize(slapi_ch_strdup(bval->bv_val)));
+ i = slapi_attr_next_value(attr, i, &sval);
+ }
+ PR_RWLock_Unlock(cb->config.rwl_config_lock);
+ } else
+ if ( !strcasecmp ( attr_name, CB_CONFIG_GLOBAL_DEBUG )) {
+ i = slapi_attr_first_value(attr, &sval);
+ if (i != -1 ) {
+ bval = (struct berval *) slapi_value_get_berval(sval);
+ /* any value */
+ cb_set_debug(1);
+ }
+ }
+ }
+ return LDAP_SUCCESS;
+}
diff --git a/ldap/servers/plugins/chainingdb/cb_conn_stateless.c b/ldap/servers/plugins/chainingdb/cb_conn_stateless.c
new file mode 100644
index 00000000..d97f947e
--- /dev/null
+++ b/ldap/servers/plugins/chainingdb/cb_conn_stateless.c
@@ -0,0 +1,1132 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include "cb.h"
+
+/*
+ * Most of the complicated connection-related code lives in this file. Some
+ * general notes about how we manage our connections to "remote" LDAP servers:
+ *
+ * 1) Each farm server we have a relationship with is managed independently.
+ *
+ * 2) We may simultaneously issue multiple requests on a single LDAP
+ * connection. Each server has a "maxconcurrency" configuration
+ * parameter associated with it that caps the number of outstanding operations
+ * per connection. For each connection we maintain a "usecount"
+ * which is used to track the number of threads using the connection.
+ *
+ * 3) IMPORTANT NOTE: This connexion management is stateless i.e there is no garanty that
+ * operation from the same incoming client connections are sent to the same
+ * outgoing connection to the farm server. Today, this is not a problem because
+ * all controls we support are stateless. The implementation of the abandon
+ * operation takes this limitation into account.
+ *
+ * 4) We may open more than one connection to a server. Each farm server
+ * has a "maxconnections" configuration parameter associated with it
+ * that caps the number of connections.
+ *
+ * 5) If no connection is available to service a request , threads
+ * go to sleep on a condition variable and one is woken up each time
+ * a connection's "usecount" is decremented.
+ *
+ * 6) If we see an LDAP_CONNECT_ERROR or LDAP_SERVER_DOWN error on a
+ * session handle, we mark its status as CB_LDAP_STATUS_DOWN and
+ * close it as soon as all threads using it release it. Connections
+ * marked as "down" are not counted against the "maxconnections" limit.
+ *
+ * 7) We close and reopen connections that have been open for more than
+ * the server's configured connection lifetime. This is done to ensure
+ * that we reconnect to a primary server after failover occurs. If no
+ * lifetime is configured or it is set to 0, we never close and reopen
+ * connections.
+ */
+
+static void cb_close_and_dispose_connection ( cb_outgoing_conn * conn );
+static void cb_check_for_stale_connections(cb_conn_pool * pool);
+
+PRUint32 PR_GetThreadID(PRThread *thread);
+
+/* returns the threadId of the current thread modulo MAX_CONN_ARRAY
+=> gives the position of the thread in the array of secure connections */
+static int PR_ThreadSelf() {
+ PRThread *thr = PR_GetCurrentThread();
+ PRUint32 myself = PR_GetThreadID(thr);
+ myself &= 0x000007FF ;
+ return myself;
+}
+
+static int PR_MyThreadId() {
+ PRThread *thr = PR_GetCurrentThread();
+ PRUint32 myself = PR_GetThreadID(thr);
+ return myself;
+}
+
+/*
+** Close outgoing connections
+*/
+
+void cb_close_conn_pool(cb_conn_pool * pool) {
+
+ cb_outgoing_conn *conn, *nextconn;
+ int secure = pool->secure;
+ int i = 0;
+
+ slapi_lock_mutex( pool->conn.conn_list_mutex );
+
+ if (secure) {
+ for (i=0; i< MAX_CONN_ARRAY; i++) {
+ for (conn = pool->connarray[i]; conn != NULL; conn = nextconn) {
+ if ( conn->status != CB_CONNSTATUS_OK ) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "cb_close_conn_pool: unexpected connection state (%d)\n",conn->status);
+ }
+ nextconn=conn->next;
+ cb_close_and_dispose_connection(conn);
+ }
+ }
+ }
+ else {
+ for ( conn = pool->conn.conn_list; conn != NULL; conn = nextconn ) {
+ if ( conn->status != CB_CONNSTATUS_OK ) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "cb_close_conn_pool: unexpected connection state (%d)\n",conn->status);
+ }
+ nextconn=conn->next;
+ cb_close_and_dispose_connection(conn);
+ }
+ }
+
+ pool->conn.conn_list=NULL;
+ pool->conn.conn_list_count=0;
+
+ slapi_unlock_mutex( pool->conn.conn_list_mutex );
+}
+
+/*
+ * Get an LDAP session handle for communicating with the farm servers.
+ *
+ * Returns an LDAP eror code, typically:
+ * LDAP_SUCCESS
+ * LDAP_TIMELIMIT_EXCEEDED
+ * LDAP_CONNECT_ERROR
+ * NOTE : if maxtime NULL, use operation timeout
+ */
+
+int cb_get_connection(cb_conn_pool * pool, LDAP ** lld, cb_outgoing_conn ** cc,struct timeval * maxtime, char **errmsg) {
+
+ int rc=LDAP_SUCCESS; /* optimistic */
+ cb_outgoing_conn *conn=NULL;
+ cb_outgoing_conn *connprev=NULL;
+ LDAP *ld=NULL;
+ time_t endbefore=0;
+ int checktime=0;
+ struct timeval bind_to, op_to;
+ unsigned int maxconcurrency,maxconnections;
+ char *password,*binddn,*hostname;
+ unsigned int port;
+ int secure;
+ static char *error1="Can't contact remote server : %s";
+ static char *error2="Can't bind to remote server : %s";
+ int isMultiThread = ENABLE_MULTITHREAD_PER_CONN ; /* by default, we enable multiple operations per connection */
+
+ /*
+ ** return an error if we can't get a connection
+ ** before the operation timeout has expired
+ ** bind_timeout: timeout for the bind operation (if bind needed)
+ ** ( checked in ldap_result )
+ ** op_timeout: timeout for the op that needs a connection
+ ** ( checked in the loop )
+ */
+ *cc=NULL;
+
+ PR_RWLock_Rlock(pool->rwl_config_lock);
+ maxconcurrency=pool->conn.maxconcurrency;
+ maxconnections=pool->conn.maxconnections;
+ bind_to.tv_sec = pool->conn.bind_timeout.tv_sec;
+ bind_to.tv_usec = pool->conn.bind_timeout.tv_usec;
+ op_to.tv_sec = pool->conn.op_timeout.tv_sec;
+ op_to.tv_usec = pool->conn.op_timeout.tv_usec;
+
+ /* SD 02/10/2000 temp fix */
+ /* allow dynamic update of the binddn & password */
+ /* host, port and security mode */
+ /* previous values are NOT freed when changed */
+ /* won't likely to be changed often */
+ /* pointers put in the waste basket fields and */
+ /* freed when the backend is stopped. */
+
+ password=pool->password;
+ binddn=pool->binddn;
+ hostname=pool->hostname;
+ port=pool->port;
+ secure=pool->secure;
+
+ PR_RWLock_Unlock(pool->rwl_config_lock);
+
+ if (secure) {
+ isMultiThread = DISABLE_MULTITHREAD_PER_CONN ;
+ }
+
+ /* For stupid admins */
+ if (maxconnections <=0) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "<== cb_get_connection error (no connection available)\n");
+ if ( errmsg ) {
+ *errmsg = slapi_ch_malloc(CB_BUFSIZE);
+ sprintf(*errmsg,error1,"no connection available");
+ }
+ return LDAP_CONNECT_ERROR;
+ }
+
+ if (maxtime) {
+ if (maxtime->tv_sec != 0) {
+ checktime=1;
+ endbefore = current_time() + maxtime->tv_sec;
+
+ /* make sure bind to <= operation timeout */
+ if ((bind_to.tv_sec==0) || (bind_to.tv_sec > maxtime->tv_sec))
+ bind_to.tv_sec=maxtime->tv_sec;
+ }
+ } else {
+ if (op_to.tv_sec != 0) {
+ checktime=1;
+ endbefore = current_time() + op_to.tv_sec;
+
+ /* make sure bind to <= operation timeout */
+ if ((bind_to.tv_sec==0) || (bind_to.tv_sec > op_to.tv_sec))
+ bind_to.tv_sec=op_to.tv_sec;
+ }
+ }
+
+ /*
+ * Close (or mark to be closed) any connections for this farm server that have
+ * exceeded the maximum connection lifetime.
+ */
+
+ cb_check_for_stale_connections(pool);
+
+ /*
+ * Look for an available, already open connection
+ */
+
+ slapi_lock_mutex( pool->conn.conn_list_mutex );
+
+ if (cb_debug_on()) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "==> cb_get_connection server %s conns: %d maxconns: %d\n",
+ hostname, pool->conn.conn_list_count, maxconnections );
+ }
+
+ for (;;) {
+
+ /* time limit mgmt */
+ if (checktime) {
+ if (current_time() > endbefore ) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "cb_get_connection server %s expired.\n", hostname );
+ if ( errmsg ) {
+ *errmsg = slapi_ch_malloc(CB_BUFSIZE);
+ sprintf(*errmsg,error1,"timelimit exceeded");
+ }
+ rc=LDAP_TIMELIMIT_EXCEEDED;
+ conn=NULL;
+ ld=NULL;
+ goto unlock_and_return;
+ }
+ }
+
+ /*
+ * First, look for an available, already open/bound connection
+ */
+
+ if (secure) {
+ for (conn = pool->connarray[PR_ThreadSelf()]; conn != NULL; conn = conn->next) {
+ if ((conn->ThreadId == PR_MyThreadId()) && (conn->status == CB_CONNSTATUS_OK &&
+ conn->refcount < maxconcurrency)){
+ if (cb_debug_on()) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "<= cb_get_connection server found conn 0x%x to use)\n", conn );
+ }
+ goto unlock_and_return; /* found one */
+ }
+ }
+ }
+ else {
+ connprev = NULL;
+ for ( conn = pool->conn.conn_list; conn != NULL; conn = conn->next ) {
+ if (cb_debug_on()) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "list: conn 0x%x status %d refcount %d\n", conn,
+ conn->status, conn->refcount );
+ }
+
+ if ( conn->status == CB_CONNSTATUS_OK
+ && conn->refcount < maxconcurrency ) {
+ if (cb_debug_on()) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "<= cb_get_connection server found conn 0x%x to use)\n", conn );
+ }
+ goto unlock_and_return; /* found one */
+ }
+ connprev = conn;
+ }
+ }
+
+ if ( secure || pool->conn.conn_list_count <maxconnections) {
+
+ int version=LDAP_VERSION3;
+
+ /* check wether the security libraries are correctly initialized */
+ if (secure && slapd_security_library_is_initialized() != 1) {
+ slapi_log_error(
+ SLAPI_LOG_FATAL, CB_PLUGIN_SUBSYSTEM,
+ "SSL Not Initialized, Chaining Backend over SSL FAILED\n");
+ rc = LDAP_CONNECT_ERROR;
+ goto unlock_and_return;
+ }
+
+ /*
+ * we have not exceeded the maximum number of connections allowed,
+ * so we initialize a new one and add it to the end of our list.
+ */
+
+ /* No need to lock. url can't be changed dynamically */
+ if ((ld=slapi_ldap_init(hostname,port,secure,isMultiThread))== NULL) {
+ if (cb_debug_on()) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "Can't contact server <%s> port <%d>.\n", hostname, port);
+ }
+ if ( errmsg ) {
+ *errmsg = slapi_ch_malloc(CB_BUFSIZE);
+ sprintf(*errmsg,error1,"unknown reason");
+ }
+ rc = LDAP_CONNECT_ERROR;
+ goto unlock_and_return;
+ }
+
+ ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, &version );
+ /* Don't chase referrals */
+ ldap_set_option( ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF );
+
+ /* no controls and simple bind only */
+ /* For now, bind even if no user to detect error */
+ /* earlier */
+ if (pool->bindit) {
+ int msgid;
+ LDAPMessage *res=NULL;
+ int parse_rc;
+ PRErrorCode prerr = 0;
+ LDAPControl **serverctrls=NULL;
+ char **referrals=NULL;
+
+ char *plain = NULL;
+ int ret = -1;
+
+ rc=LDAP_SUCCESS;
+
+ if (cb_debug_on()) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "Bind to to server <%s> port <%d> as <%s>\n",
+ hostname, port, binddn);
+ }
+
+ ret = pw_rever_decode(password, &plain, CB_CONFIG_USERPASSWORD);
+
+ /* Pb occured in decryption: stop now, binding will fail */
+ if ( ret == -1 )
+ {
+ if (cb_debug_on()) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "Internal credentials decoding error\n.",
+ 0, 0, 0);
+ }
+ rc = LDAP_LOCAL_ERROR;
+ goto unlock_and_return;
+ }
+
+ /* Password-based client authentication */
+
+ if (( msgid = ldap_simple_bind( ld, binddn, plain)) <0) {
+ rc=ldap_get_lderrno( ld, NULL, NULL );
+ prerr=PR_GetError();
+ }
+ if ( ret == 0 ) free(plain); /* free plain only if it has been duplicated */
+
+ if ( rc != LDAP_SUCCESS ) {
+ if (cb_debug_on()) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "Can't bind to server <%s> port <%d>. "
+ "(LDAP error %d - %s; "
+ SLAPI_COMPONENT_NAME_NSPR " error %d - %s)\n",
+ hostname, port, rc,
+ ldap_err2string(rc),
+ prerr, slapd_pr_strerror(prerr));
+ }
+ if ( errmsg ) {
+ *errmsg = slapi_ch_malloc(CB_BUFSIZE);
+ sprintf(*errmsg,error2, ldap_err2string(rc));
+ }
+ rc = LDAP_CONNECT_ERROR;
+ goto unlock_and_return;
+ }
+
+ rc = ldap_result( ld, msgid, 0, &bind_to, &res );
+ switch (rc) {
+ case -1:
+ rc = ldap_get_lderrno( ld, NULL, NULL );
+ if (cb_debug_on()) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "Can't bind to server <%s> port <%d>. "
+ "(LDAP error %d - %s; "
+ SLAPI_COMPONENT_NAME_NSPR " error %d - %s)\n",
+ hostname, port, rc,
+ ldap_err2string(rc),
+ prerr, slapd_pr_strerror(prerr));
+ }
+ if ( errmsg ) {
+ *errmsg = slapi_ch_malloc(CB_BUFSIZE);
+ sprintf(*errmsg,error2,ldap_err2string(rc));
+ }
+ rc = LDAP_CONNECT_ERROR;
+ goto unlock_and_return;
+ case 0:
+ if (cb_debug_on()) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "Can't bind to server <%s> port <%d>. (%s)\n",
+ hostname, port, "time-out expired");
+ }
+ rc = LDAP_CONNECT_ERROR;
+ goto unlock_and_return;
+ default:
+
+ parse_rc = ldap_parse_result( ld, res, &rc, NULL,
+ NULL, &referrals, &serverctrls, 1 );
+
+ if ( parse_rc != LDAP_SUCCESS ) {
+ if (cb_debug_on()) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "Can't bind to server <%s> port <%d>. (%s)\n",
+ hostname, port, ldap_err2string(parse_rc));
+ }
+ if ( errmsg ) {
+ *errmsg = slapi_ch_malloc(CB_BUFSIZE);
+ sprintf(*errmsg,error2,ldap_err2string(parse_rc));
+ }
+ rc = parse_rc;
+ goto unlock_and_return;
+ }
+
+ if ( rc != LDAP_SUCCESS ) {
+ if (cb_debug_on()) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "Can't bind to server <%s> port <%d>. (%s)\n",
+ hostname, port, ldap_err2string(rc));
+ }
+ if ( errmsg ) {
+ *errmsg = slapi_ch_malloc(CB_BUFSIZE);
+ sprintf(*errmsg,error2, ldap_err2string(rc));
+ }
+ goto unlock_and_return;
+ }
+
+ if ( serverctrls )
+ {
+ int i;
+ for( i = 0; serverctrls[ i ] != NULL; ++i )
+ {
+ if ( !(strcmp( serverctrls[ i ]->ldctl_oid, LDAP_CONTROL_PWEXPIRED)) )
+ {
+ /* Bind is successful but password has expired */
+ slapi_log_error(SLAPI_LOG_FATAL, CB_PLUGIN_SUBSYSTEM,
+ "Succesfully bound as %s to remote server %s:%d, "
+ "but password has expired.\n",
+ binddn, hostname, port);
+ }
+ else if ( !(strcmp( serverctrls[ i ]->ldctl_oid, LDAP_CONTROL_PWEXPIRING)) )
+ {
+ /* The password is expiring in n seconds */
+ if ( (serverctrls[ i ]->ldctl_value.bv_val != NULL) &&
+ (serverctrls[ i ]->ldctl_value.bv_len > 0) )
+ {
+ int password_expiring = atoi( serverctrls[ i ]->ldctl_value.bv_val );
+ slapi_log_error(SLAPI_LOG_FATAL, CB_PLUGIN_SUBSYSTEM,
+ "Succesfully bound as %s to remote server %s:%d, "
+ "but password is expiring in %d seconds.\n",
+ binddn, hostname, port, password_expiring);
+ }
+ }
+ }
+ ldap_controls_free(serverctrls);
+ }
+
+ if (referrals)
+ charray_free(referrals);
+ }
+ }
+
+ conn = (cb_outgoing_conn *) slapi_ch_malloc(sizeof(cb_outgoing_conn));
+ conn->ld=ld;
+ conn->status=CB_CONNSTATUS_OK;
+ conn->refcount=0; /* incremented below */
+ conn->opentime=current_time();
+ conn->ThreadId=PR_MyThreadId(); /* store the thread id */
+ conn->next=NULL;
+ if (secure) {
+ if (pool->connarray[PR_ThreadSelf()] == NULL) {
+ pool->connarray[PR_ThreadSelf()] = conn;
+ }
+ else {
+ conn->next = pool->connarray[PR_ThreadSelf()];
+ pool->connarray[PR_ThreadSelf()] = conn ;
+ }
+ }
+ else {
+ if ( NULL == connprev ) {
+ pool->conn.conn_list = conn;
+ } else {
+ connprev->next=conn;
+ }
+ }
+
+ ++pool->conn.conn_list_count;
+
+ if (cb_debug_on()) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "<= cb_get_connection added new conn 0x%x, "
+ "conn count now %d\n", conn->ld, pool->conn.conn_list_count );
+ }
+ goto unlock_and_return; /* got a new one */
+ }
+
+ if (cb_debug_on()) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "... cb_get_connection waiting for conn to free up\n" );
+ }
+
+ if (!secure) slapi_wait_condvar( pool->conn.conn_list_cv, NULL );
+
+ if (cb_debug_on()) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "... cb_get_connection awake again\n" );
+ }
+ }
+
+unlock_and_return:
+ if ( conn != NULL ) {
+ ++conn->refcount;
+ *lld=conn->ld;
+ *cc=conn;
+ if (cb_debug_on()) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "<== cb_get_connection ld=0x%x (concurrency now %d)\n",*lld, conn->refcount );
+ }
+
+ } else {
+ if ( NULL != ld ) {
+ slapi_ldap_unbind( ld );
+ }
+
+ if (cb_debug_on()) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "<== cb_get_connection error %d\n", rc );
+ }
+ }
+
+ slapi_unlock_mutex(pool->conn.conn_list_mutex);
+ return( rc );
+}
+
+/*
+ * We are done with the connection handle because the
+ * LDAP operation has completed.
+ */
+
+void cb_release_op_connection(cb_conn_pool* pool, LDAP *lld, int dispose) {
+
+ cb_outgoing_conn *conn;
+ cb_outgoing_conn *connprev = NULL;
+ int secure = pool->secure;
+ int myself = 0;
+
+ slapi_lock_mutex(pool->conn.conn_list_mutex);
+ /*
+ * find the connection structure this ld is part of
+ */
+
+ if (secure) {
+ myself = PR_ThreadSelf();
+ for (conn = pool->connarray[myself]; conn != NULL; conn = conn->next ) {
+ if ( lld == conn->ld )
+ break;
+ connprev = conn;
+ }
+ }
+ else {
+ for ( conn = pool->conn.conn_list; conn != NULL; conn = conn->next ){
+ if ( lld == conn->ld )
+ break;
+ connprev = conn;
+ }
+ }
+
+ if ( conn == NULL ) { /* ld not found -- unexpected */
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "==> cb_release_op_connection ld=0x%x not found\n", lld );
+ } else {
+
+ --conn->refcount;
+
+ if (cb_debug_on()) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "release conn 0x%x status %d refcount after release %d\n", conn,
+ conn->status, conn->refcount );
+ }
+
+ if ( dispose ) {
+ conn->status = CB_CONNSTATUS_DOWN;
+ }
+
+ if ( conn->status != CB_CONNSTATUS_OK && conn->refcount == 0 ) {
+
+ /*
+ * remove from server's connection list
+ */
+
+ if (!secure) {
+ if ( connprev == NULL ) {
+ pool->conn.conn_list = conn->next;
+ } else {
+ connprev->next = conn->next;
+ }
+ }
+ else {
+ if ( connprev == NULL ) {
+ pool->connarray[myself] = conn->next;
+ } else {
+ connprev->next = conn->next;
+ }
+ }
+
+ --pool->conn.conn_list_count;
+
+ /*
+ * close connection and free memory
+ */
+ cb_close_and_dispose_connection( conn );
+ }
+ }
+
+ /*
+ * wake up a thread that is waiting for a connection
+ */
+
+ if (!secure) slapi_notify_condvar( pool->conn.conn_list_cv, 0 );
+
+ slapi_unlock_mutex( pool->conn.conn_list_mutex );
+}
+
+
+static void
+cb_close_and_dispose_connection( cb_outgoing_conn *conn )
+{
+ slapi_ldap_unbind( conn->ld );
+ conn->ld = NULL;
+ slapi_ch_free( (void **)&conn );
+}
+
+static void cb_check_for_stale_connections(cb_conn_pool * pool) {
+
+ cb_outgoing_conn * connprev, *conn, *conn_next;
+ time_t curtime;
+ int connlifetime;
+ int myself;
+
+ PR_RWLock_Rlock(pool->rwl_config_lock);
+ connlifetime=pool->conn.connlifetime;
+ PR_RWLock_Unlock(pool->rwl_config_lock);
+
+ connprev = NULL;
+ conn_next = NULL;
+
+ slapi_lock_mutex(pool->conn.conn_list_mutex);
+
+ if (connlifetime > 0)
+ curtime=current_time();
+
+ if (pool->secure) {
+ myself = PR_ThreadSelf();
+ for (conn = pool->connarray[myself]; conn != NULL; conn = conn_next){
+ if ((conn->status == CB_CONNSTATUS_STALE) ||
+ (( connlifetime > 0) && (curtime - conn->opentime > connlifetime))) {
+ if ( conn->refcount == 0 ) {
+ if (cb_debug_on()) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "cb_check_for_stale_connections: conn 0x%x idle and stale\n",conn);
+ }
+ --pool->conn.conn_list_count;
+ if (connprev == NULL) {
+ pool->connarray[myself] = conn->next ;
+ }
+ else {
+ connprev->next = conn->next ;
+ }
+ conn_next = conn->next ;
+ cb_close_and_dispose_connection( conn );
+ continue;
+ }
+ /* Connection is stale but in use */
+ /* Mark to be disposed later but let it in the backend list */
+ /* so that it is counted as a valid connection */
+ else {
+ conn->status = CB_CONNSTATUS_STALE;
+ }
+ if (cb_debug_on()) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "cb_check_for_stale_connections: conn 0x%x stale\n",conn);
+ }
+ }
+ connprev = conn ;
+ conn_next = conn->next;
+ }
+ slapi_unlock_mutex(pool->conn.conn_list_mutex);
+ return;
+ }
+
+ for ( conn = pool->conn.conn_list; conn != NULL; conn=conn_next ) {
+ if ((conn->status == CB_CONNSTATUS_STALE) ||
+ (( connlifetime > 0) && (curtime - conn->opentime > connlifetime))) {
+ if ( conn->refcount == 0 ) {
+
+ /* Connection idle & stale. Remove and free. */
+
+ if ( NULL == connprev )
+ pool->conn.conn_list = conn->next;
+ else
+ connprev->next=conn->next;
+
+ if (cb_debug_on()) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "cb_check_for_stale_connections: conn 0x%x idle and stale\n",conn);
+ }
+ --pool->conn.conn_list_count;
+ conn_next=conn->next;
+ cb_close_and_dispose_connection( conn );
+ continue;
+ }
+
+ /* Connection is stale but in use */
+ /* Mark to be disposed later but let it in the backend list */
+ /* so that it is counted as a valid connection */
+ else {
+ conn->status = CB_CONNSTATUS_STALE;
+ }
+ if (cb_debug_on()) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "cb_check_for_stale_connections: conn 0x%x stale\n",conn);
+ }
+ }
+ connprev = conn;
+ conn_next=conn->next;
+ }
+
+ /* Generate an event to wake up threads waiting */
+ /* for a conn to be released. Useful to detect */
+ /* exceeded time limit. May be expensive */
+
+ slapi_notify_condvar( pool->conn.conn_list_cv, 0 );
+
+ slapi_unlock_mutex(pool->conn.conn_list_mutex);
+}
+
+/*
+ * close all open connections in preparation for server shutdown, etc.
+ * WARNING: Don't wait for current operations to complete
+ */
+
+void
+cb_close_all_connections( Slapi_Backend * be )
+{
+
+ cb_outgoing_conn *conn, *next_conn;
+ cb_backend_instance * cb= cb_get_instance(be);
+ int i;
+
+ slapi_lock_mutex(cb->pool->conn.conn_list_mutex);
+ if (cb->pool->secure) {
+ for (i=0; i< MAX_CONN_ARRAY; i++) {
+ for (conn = cb->pool->connarray[i]; conn != NULL; conn = next_conn ){
+ next_conn = conn->next;
+ cb_close_and_dispose_connection(conn);
+ }
+ }
+ } else {
+ for ( conn = cb->pool->conn.conn_list; conn != NULL; conn = next_conn ) {
+ next_conn=conn->next;
+ cb_close_and_dispose_connection(conn);
+ }
+ }
+ slapi_unlock_mutex(cb->pool->conn.conn_list_mutex);
+
+ slapi_lock_mutex(cb->bind_pool->conn.conn_list_mutex);
+ if (cb->bind_pool->secure) {
+ for (i=0; i< MAX_CONN_ARRAY; i++) {
+ for (conn = cb->bind_pool->connarray[i]; conn != NULL; conn = next_conn ){
+ next_conn=conn->next;
+ cb_close_and_dispose_connection(conn);
+ }
+ }
+ } else {
+ for ( conn = cb->bind_pool->conn.conn_list; conn != NULL; conn = next_conn ) {
+ next_conn=conn->next;
+ cb_close_and_dispose_connection(conn);
+ }
+ }
+ slapi_unlock_mutex(cb->bind_pool->conn.conn_list_mutex);
+}
+
+/* Mark used connections as stale and close unsued connections */
+/* Called when the target farm url has changed */
+
+void cb_stale_all_connections( cb_backend_instance * cb)
+{
+ cb_outgoing_conn *conn, *next_conn, *prev_conn;
+ int notify=0;
+ int i, j;
+ cb_conn_pool *pools[3];
+
+ pools[0]=cb->pool;
+ pools[1]=cb->bind_pool;
+ pools[2]=NULL;
+
+ for (i=0; pools[i]; i++) {
+ slapi_lock_mutex(pools[i]->conn.conn_list_mutex);
+ for (j=0; j< MAX_CONN_ARRAY; j++) {
+ prev_conn=NULL;
+ for (conn = pools[i]->connarray[j]; conn != NULL; conn=next_conn) {
+ next_conn=conn->next;
+ if (conn->refcount > 0) {
+ /*
+ ** Connection is stale but in use
+ ** Mark to be disposed later but let it in the backend list
+ ** so that it is counted as a valid connection
+ */
+ conn->status = CB_CONNSTATUS_STALE;
+ prev_conn=conn;
+ } else {
+ if (prev_conn == NULL) {
+ pools[i]->connarray[j]=next_conn;
+ } else {
+ prev_conn->next=next_conn;
+ }
+ cb_close_and_dispose_connection(conn);
+ pools[i]->conn.conn_list_count--;
+ }
+ }
+ }
+ prev_conn = NULL ;
+ for ( conn = pools[i]->conn.conn_list; conn != NULL; conn = next_conn ) {
+ next_conn=conn->next;
+ if (conn->refcount > 0) {
+ /*
+ ** Connection is stale but in use
+ ** Mark to be disposed later but let it in the backend list
+ ** so that it is counted as a valid connection
+ */
+ conn->status = CB_CONNSTATUS_STALE;
+ prev_conn=conn;
+ }
+ else {
+ if (conn==pools[i]->conn.conn_list) {
+ pools[i]->conn.conn_list=next_conn;
+ } else {
+ prev_conn->next=next_conn;
+ }
+ cb_close_and_dispose_connection(conn);
+ pools[i]->conn.conn_list_count--;
+ notify=1;
+ }
+ }
+ if (notify && (! pools[i]->secure)) {
+ slapi_notify_condvar( pools[i]->conn.conn_list_cv, 0 );
+ }
+ slapi_unlock_mutex(pools[i]->conn.conn_list_mutex);
+ }
+}
+
+
+
+/**************************************************************************/
+/* Need to use our own connect function until we've switched to C-SDK 4.1 */
+/* to have a timeout in the connect system call. */
+/**************************************************************************/
+
+static int global_connect_to;
+
+#if 0
+
+/* Taken from C-SDK 4.1 */
+#include <fcntl.h>
+#include <errno.h>
+#define LDAP_X_IO_TIMEOUT_NO_TIMEOUT (-1)
+
+static int
+nsldapi_os_connect_with_to(LBER_SOCKET sockfd, struct sockaddr *saptr,
+ int salen)
+{
+#ifndef _WIN32
+ int flags;
+#endif /* _WIN32 */
+ int n, error;
+ int len;
+ fd_set rset, wset;
+ struct timeval tval;
+#ifdef _WIN32
+ int nonblock = 1;
+ int block = 0;
+ fd_set eset;
+#endif /* _WIN32 */
+
+ int msec=global_connect_to; /* global */
+
+#ifdef _WIN32
+ ioctlsocket(sockfd, FIONBIO, &nonblock);
+#else
+ flags = fcntl(sockfd, F_GETFL, 0);
+ fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
+#endif /* _WIN32 */
+
+ error = 0;
+ if ((n = connect(sockfd, saptr, salen)) < 0)
+#ifdef _WIN32
+ if ((n != SOCKET_ERROR) && (WSAGetLastError() != WSAEWOULDBLOCK)) {
+#else
+ if (errno != EINPROGRESS) {
+#endif /* _WIN32 */
+ return (-1);
+ }
+
+ /* success */
+ if (n == 0)
+ goto done;
+
+ FD_ZERO(&rset);
+ FD_SET(sockfd, &rset);
+ wset = rset;
+
+#ifdef _WIN32
+ eset = rset;
+#endif /* _WIN32 */
+
+ if (msec < 0 && msec != LDAP_X_IO_TIMEOUT_NO_TIMEOUT) {
+ msec = LDAP_X_IO_TIMEOUT_NO_TIMEOUT;
+ } else {
+ if (msec != 0)
+ tval.tv_sec = msec / 1000;
+ else
+ tval.tv_sec = 0;
+ tval.tv_usec = 0;
+ }
+
+ /* if timeval structure == NULL, select will block indefinitely */
+ /* != NULL, and value == 0, select will */
+ /* not block */
+ /* Windows is a bit quirky on how it behaves w.r.t nonblocking */
+ /* connects. If the connect fails, the exception fd, eset, is */
+ /* set to show the failure. The first argument in select is */
+ /* ignored */
+
+#ifdef _WIN32
+ if ((n = select(sockfd +1, &rset, &wset, &eset,
+ (msec != LDAP_X_IO_TIMEOUT_NO_TIMEOUT) ? &tval : NULL)) == 0) {
+ errno = WSAETIMEDOUT;
+ return (-1);
+ }
+ /* if wset is set, the connect worked */
+ if (FD_ISSET(sockfd, &wset) || FD_ISSET(sockfd, &rset)) {
+ len = sizeof(error);
+ if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (char *)&error, &len)
+ < 0)
+ return (-1);
+ goto done;
+ }
+
+ /* if eset is set, the connect failed */
+ if (FD_ISSET(sockfd, &eset)) {
+ return (-1);
+ }
+
+ /* failure on select call */
+ if (n == SOCKET_ERROR) {
+ return (-1);
+ }
+#else
+ if ((n = select(sockfd +1, &rset, &wset, NULL,
+ (msec != LDAP_X_IO_TIMEOUT_NO_TIMEOUT) ? &tval : NULL)) == 0) {
+ errno = ETIMEDOUT;
+ return (-1);
+ }
+ if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) {
+ len = sizeof(error);
+ if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (char *)&error, &len)
+ < 0)
+ return (-1);
+ }
+#endif /* _WIN32 */
+done:
+#ifdef _WIN32
+ ioctlsocket(sockfd, FIONBIO, &block);
+#else
+ fcntl(sockfd, F_SETFL, flags);
+#endif /* _WIN32 */
+
+ if (error) {
+ errno = error;
+ return (-1);
+ }
+
+ return (0);
+}
+
+#endif
+
+/* Try to figure out if a farm server is still alive */
+
+int cb_ping_farm(cb_backend_instance *cb, cb_outgoing_conn * cnx,time_t end_time) {
+
+ char *attrs[] ={"1.1",NULL};
+ int rc;
+ struct timeval timeout;
+ LDAP *ld;
+ LDAPMessage *result;
+#if 0
+ struct ldap_io_fns iof;
+#endif
+ time_t now;
+ if (cb->max_idle_time <=0) /* Heart-beat disabled */
+ return LDAP_SUCCESS;
+
+ if (cnx && (cnx->status != CB_CONNSTATUS_OK )) /* Known problem */
+ return LDAP_SERVER_DOWN;
+
+ now = current_time();
+ if (end_time && ((now <= end_time) || (end_time <0))) return LDAP_SUCCESS;
+
+ ld=slapi_ldap_init(cb->pool->hostname,cb->pool->port,cb->pool->secure,0);
+ if (NULL == ld) {
+ cb_update_failed_conn_cpt( cb );
+ return LDAP_SERVER_DOWN;
+ }
+
+#if 0
+ memset(&iof,0,sizeof(struct ldap_io_fns));
+ if (LDAP_SUCCESS !=ldap_get_option(ld,LDAP_OPT_IO_FN_PTRS,&iof)) {
+ slapi_ldap_unbind( ld );
+ cb_update_failed_conn_cpt( cb );
+ return LDAP_SERVER_DOWN;
+ }
+
+ iof.liof_connect = nsldapi_os_connect_with_to;
+ if (LDAP_SUCCESS !=ldap_set_option(ld,LDAP_OPT_IO_FN_PTRS,&iof)) {
+ slapi_ldap_unbind( ld );
+ cb_update_failed_conn_cpt( cb );
+ return LDAP_SERVER_DOWN;
+ }
+
+#endif
+
+ timeout.tv_sec=cb->max_test_time;
+ timeout.tv_usec=0;
+
+ global_connect_to=cb->max_test_time * 1000; /* Reuse the same for the connect */
+ rc=ldap_search_ext_s(ld ,NULL,LDAP_SCOPE_BASE,"objectclass=*",attrs,1,NULL,
+ NULL, &timeout, 1,&result);
+ if ( LDAP_SUCCESS != rc ) {
+ slapi_ldap_unbind( ld );
+ cb_update_failed_conn_cpt( cb );
+ return LDAP_SERVER_DOWN;
+ }
+
+ ldap_msgfree(result);
+ slapi_ldap_unbind( ld );
+ cb_reset_conn_cpt( cb );
+ return LDAP_SUCCESS;
+}
+
+
+
+void cb_update_failed_conn_cpt ( cb_backend_instance *cb ) {
+ /* if the chaining BE is already unavailable, we do nothing*/
+ time_t now;
+ if (cb->monitor_availability.farmserver_state == FARMSERVER_AVAILABLE) {
+ slapi_lock_mutex(cb->monitor_availability.cpt_lock);
+ cb->monitor_availability.cpt ++;
+ slapi_unlock_mutex(cb->monitor_availability.cpt_lock);
+ if (cb->monitor_availability.cpt >= CB_NUM_CONN_BEFORE_UNAVAILABILITY ) {
+ /* we reach the limit of authorized failed connections => we setup the chaining BE state to unavailable */
+ now = current_time();
+ slapi_lock_mutex(cb->monitor_availability.lock_timeLimit);
+ cb->monitor_availability.unavailableTimeLimit = now + CB_UNAVAILABLE_PERIOD ;
+ slapi_unlock_mutex(cb->monitor_availability.lock_timeLimit);
+ cb->monitor_availability.farmserver_state = FARMSERVER_UNAVAILABLE ;
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "cb_update_failed_conn_cpt: Farm server unavailable");
+ }
+
+ }
+}
+
+void cb_reset_conn_cpt( cb_backend_instance *cb ) {
+ if (cb->monitor_availability.cpt > 0) {
+ slapi_lock_mutex(cb->monitor_availability.cpt_lock);
+ cb->monitor_availability.cpt = 0 ;
+ if (cb->monitor_availability.farmserver_state == FARMSERVER_UNAVAILABLE) {
+ cb->monitor_availability.farmserver_state = FARMSERVER_AVAILABLE ;
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "cb_reset_conn_cpt: Farm server is back");
+ }
+ slapi_unlock_mutex(cb->monitor_availability.cpt_lock);
+ }
+}
+
+int cb_check_availability( cb_backend_instance *cb, Slapi_PBlock *pb ) {
+ /* check wether the farmserver is available or not */
+ int rc ;
+ time_t now ;
+ if ( cb->monitor_availability.farmserver_state == FARMSERVER_UNAVAILABLE ){
+ slapi_lock_mutex(cb->monitor_availability.lock_timeLimit);
+ now = current_time();
+ if (now >= cb->monitor_availability.unavailableTimeLimit) {
+ cb->monitor_availability.unavailableTimeLimit = now + CB_INFINITE_TIME ; /* to be sure only one thread can do the test */
+ slapi_unlock_mutex(cb->monitor_availability.lock_timeLimit);
+ }
+ else {
+ slapi_unlock_mutex(cb->monitor_availability.lock_timeLimit);
+ cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL, "FARM SERVER TEMPORARY UNAVAILABLE", 0, NULL) ;
+ return FARMSERVER_UNAVAILABLE ;
+ }
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "cb_check_availability: ping the farm server and check if it's still unavailable");
+ if ((rc = cb_ping_farm(cb, NULL, 0)) != LDAP_SUCCESS) { /* farm still unavailable... Just change the timelimit */
+ slapi_lock_mutex(cb->monitor_availability.lock_timeLimit);
+ now = current_time();
+ cb->monitor_availability.unavailableTimeLimit = now + CB_UNAVAILABLE_PERIOD ;
+ slapi_unlock_mutex(cb->monitor_availability.lock_timeLimit);
+ cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL, "FARM SERVER TEMPORARY UNAVAILABLE", 0, NULL) ;
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "cb_check_availability: Farm server still unavailable");
+ return FARMSERVER_UNAVAILABLE ;
+ }
+ else {
+ /* farm is back !*/
+ slapi_lock_mutex(cb->monitor_availability.lock_timeLimit);
+ now = current_time();
+ cb->monitor_availability.unavailableTimeLimit = now ; /* the unavailable period is finished */
+ slapi_unlock_mutex(cb->monitor_availability.lock_timeLimit);
+ /* The farmer server state backs to FARMSERVER_AVAILABLE, but this already done in cb_ping_farm, and also the reset of cpt*/
+ return FARMSERVER_AVAILABLE ;
+ }
+ }
+ return FARMSERVER_AVAILABLE ;
+}
diff --git a/ldap/servers/plugins/chainingdb/cb_controls.c b/ldap/servers/plugins/chainingdb/cb_controls.c
new file mode 100644
index 00000000..ab612099
--- /dev/null
+++ b/ldap/servers/plugins/chainingdb/cb_controls.c
@@ -0,0 +1,289 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include "cb.h"
+
+/*
+** Controls that can't be forwarded due to the current implementation
+*/
+
+static char * unsupported_ctrls[] = {LDAP_CONTROL_PERSISTENTSEARCH,NULL};
+
+int cb_is_control_forwardable(cb_backend * cb, char *controloid) {
+ return (!(charray_inlist(unsupported_ctrls,controloid)));
+}
+
+void
+cb_register_supported_control( cb_backend * cb, char *controloid, unsigned long controlops )
+{
+ /* For now, ignore controlops */
+ if ( controloid != NULL ) {
+ PR_RWLock_Wlock(cb->config.rwl_config_lock);
+ charray_add( &cb->config.forward_ctrls,slapi_ch_strdup( controloid ));
+ PR_RWLock_Unlock(cb->config.rwl_config_lock);
+ }
+}
+
+
+void
+cb_unregister_all_supported_control( cb_backend * cb ) {
+
+ PR_RWLock_Wlock(cb->config.rwl_config_lock);
+ charray_free(cb->config.forward_ctrls);
+ cb->config.forward_ctrls=NULL;
+ PR_RWLock_Unlock(cb->config.rwl_config_lock);
+}
+
+void
+cb_unregister_supported_control( cb_backend * cb, char *controloid, unsigned long controlops )
+{
+
+ /* For now, ignore controlops */
+ if ( controloid != NULL ) {
+ int i;
+ PR_RWLock_Wlock(cb->config.rwl_config_lock);
+ for ( i = 0; cb->config.forward_ctrls != NULL && cb->config.forward_ctrls[i] != NULL; ++i ) {
+ if ( strcmp( cb->config.forward_ctrls[i], controloid ) == 0 ) {
+ break;
+ }
+ }
+ if ( cb->config.forward_ctrls == NULL || cb->config.forward_ctrls[i] == NULL) {
+ PR_RWLock_Unlock(cb->config.rwl_config_lock);
+ return;
+ }
+ if ( controlops == 0 ) {
+ charray_remove(cb->config.forward_ctrls,controloid);
+ }
+ PR_RWLock_Unlock(cb->config.rwl_config_lock);
+ }
+}
+
+int cb_create_loop_control (
+ const int hops,
+ LDAPControl **ctrlp)
+
+{
+ BerElement *ber;
+ int rc;
+
+ if ((ber = ber_alloc()) == NULL)
+ return -1;
+
+ if ( ber_printf( ber, "i", hops ) < 0) {
+ ber_free(ber,1);
+ return -1;
+ }
+
+ rc = slapi_build_control( CB_LDAP_CONTROL_CHAIN_SERVER, ber, 0, ctrlp);
+
+ ber_free(ber,1);
+
+ return rc;
+}
+
+/*
+** Return the controls to be passed to the remote
+** farm server and the LDAP error to return.
+**
+** Add the Proxied Authorization control when impersonation
+** is enabled. Other controls present in the request are added
+** to the control list
+**
+** #622885 .abandon should not inherit the to-be-abandoned-operation's controls
+** .controls attached to abandon should not be critical
+*/
+
+int cb_update_controls( Slapi_PBlock * pb,
+ LDAP * ld,
+ LDAPControl *** controls,
+ int ctrl_flags
+ )
+{
+
+ int cCount=0;
+ int dCount=0;
+ int i;
+ char * proxyDN=NULL;
+ LDAPControl ** reqControls = NULL;
+ LDAPControl ** ctrls = NULL;
+ cb_backend_instance * cb;
+ cb_backend * cbb;
+ Slapi_Backend * be;
+ int rc=LDAP_SUCCESS;
+ int hops=0;
+ int useloop=0;
+ int addauth = (ctrl_flags & CB_UPDATE_CONTROLS_ADDAUTH);
+ int isabandon = (ctrl_flags & CB_UPDATE_CONTROLS_ISABANDON);
+ int op_type = 0;
+
+ *controls = NULL;
+ slapi_pblock_get(pb, SLAPI_OPERATION_TYPE, &op_type);
+ if (!isabandon || op_type == SLAPI_OPERATION_ABANDON) {
+ /* if not abandon or abandon sent by client */
+ slapi_pblock_get( pb, SLAPI_REQCONTROLS, &reqControls );
+ }
+ slapi_pblock_get( pb, SLAPI_BACKEND, &be );
+ slapi_pblock_get( pb, SLAPI_PLUGIN_PRIVATE, &cbb );
+ cb = cb_get_instance(be);
+
+ /*****************************************/
+ /* First, check for unsupported controls */
+ /* Return an error if critical control */
+ /* else remove it from the control list */
+ /*****************************************/
+
+ for ( cCount=0; reqControls && reqControls[cCount]; cCount++ );
+ ctrls = (LDAPControl **)slapi_ch_calloc(1,sizeof(LDAPControl *) * (cCount +3));
+
+ PR_RWLock_Rlock(cbb->config.rwl_config_lock);
+
+ for ( cCount=0; reqControls && reqControls[cCount]; cCount++ ) {
+
+ /* XXXSD CASCADING */
+ /* For now, allow PROXY_AUTH control forwarding only when */
+ /* local acl evaluation to prevent unauthorized access */
+
+ if (!strcmp(reqControls[cCount]->ldctl_oid,LDAP_CONTROL_PROXYAUTH)) {
+
+ /* we have to force remote acl checking if the associated backend to this
+ chaining backend is disabled - disabled == no acl check possible */
+ if (!cb->local_acl && !cb->associated_be_is_disabled) {
+ slapi_log_error( SLAPI_LOG_PLUGIN,CB_PLUGIN_SUBSYSTEM,
+ "local aci check required to handle proxied auth control. Deny access.\n");
+ rc= LDAP_INSUFFICIENT_ACCESS;
+ break;
+ }
+
+ /* XXXSD Not safe to use proxied authorization with Directory Manager */
+ /* checked earlier when impersonation is on */
+
+ if (!cb->impersonate) {
+ char * requestor,*rootdn;
+ char * requestorCopy=NULL;
+
+ rootdn=cb_get_rootdn();
+ slapi_pblock_get( pb, SLAPI_REQUESTOR_DN, &requestor );
+ requestorCopy=slapi_ch_strdup(requestor);
+ slapi_dn_normalize_case(requestorCopy);
+
+ if (!strcmp( requestorCopy, rootdn )) { /* UTF8- aware */
+ slapi_log_error( SLAPI_LOG_PLUGIN,CB_PLUGIN_SUBSYSTEM,
+ "Use of user <%s> incompatible with proxied auth. control\n",rootdn);
+ rc=LDAP_UNAVAILABLE_CRITICAL_EXTENSION;
+ slapi_ch_free((void **)&requestorCopy);
+ break;
+ }
+ slapi_ch_free((void **)&rootdn);
+ slapi_ch_free((void **)&requestorCopy);
+ }
+
+ addauth=0;
+ ctrls[dCount]=slapi_dup_control(reqControls[cCount]);
+ dCount++;
+
+ } else
+ if (!strcmp(reqControls[cCount]->ldctl_oid,CB_LDAP_CONTROL_CHAIN_SERVER)) {
+
+ /* Max hop count reached ? */
+ /* Checked realier by a call to cb_forward_operation() */
+
+ BerElement *ber = NULL;
+
+ ber = ber_init(&(reqControls[cCount]->ldctl_value));
+ ber_scanf(ber,"i",&hops);
+ ber_free(ber,1);
+ useloop=1;
+
+ /* Add to the control list later */
+
+ } else {
+
+ int i;
+ for ( i = 0; cbb->config.forward_ctrls != NULL
+ && cbb->config.forward_ctrls[i] != NULL; ++i ) {
+ if ( strcmp( cbb->config.forward_ctrls[i], reqControls[cCount]->ldctl_oid ) == 0 ) {
+ break;
+ }
+ }
+ /* For now, ignore optype */
+ if ( cbb->config.forward_ctrls == NULL || cbb->config.forward_ctrls[i] == NULL) {
+ if (reqControls[cCount]->ldctl_iscritical) {
+ rc = LDAP_UNAVAILABLE_CRITICAL_EXTENSION;
+ break;
+ }
+ /* Skip it */
+ } else {
+ ctrls[dCount]=slapi_dup_control(reqControls[cCount]);
+ dCount++;
+ }
+ }
+ }
+
+ PR_RWLock_Unlock(cbb->config.rwl_config_lock);
+
+ if (LDAP_SUCCESS != rc) {
+ ldap_controls_free(ctrls);
+ return rc;
+ }
+
+ /***************************************/
+ /* add impersonation control if needed */
+ /***************************************/
+
+ if ( !(cb->impersonate) ) {
+
+ /* don't add proxy control */
+ addauth=0;
+ }
+
+ if (addauth) {
+ slapi_pblock_get( pb, SLAPI_REQUESTOR_DN, &proxyDN );
+
+ if ( ldap_create_proxyauth_control(ld, proxyDN, isabandon?0:1, &ctrls[dCount] )) {
+ ldap_controls_free(ctrls);
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "LDAP_CONTROL_PROXYAUTH control encoding failed.\n");
+ return LDAP_OPERATIONS_ERROR;
+ }
+ dCount++;
+ }
+
+ /***********************************************************/
+ /* add loop control if needed */
+ /* Don't add it if not in the list of forwardable controls */
+ /***********************************************************/
+
+ if (!useloop) {
+ for ( i = 0; cbb->config.forward_ctrls != NULL
+ && cbb->config.forward_ctrls[i] != NULL; ++i ) {
+ if ( strcmp( cbb->config.forward_ctrls[i],
+ CB_LDAP_CONTROL_CHAIN_SERVER) == 0 ) {
+ break;
+ }
+ }
+ }
+ if ( useloop || (cbb->config.forward_ctrls !=NULL && cbb->config.forward_ctrls[i] !=NULL)){
+
+ if (hops > 0) {
+ hops--;
+ } else {
+ hops = cb->hoplimit;
+ }
+
+ /* loop control's critical flag is 0;
+ * no special treatment is needed for abandon */
+ cb_create_loop_control(hops,&ctrls[dCount]);
+ dCount++;
+ }
+
+ if (dCount==0) {
+ ldap_controls_free(ctrls);
+ } else {
+ *controls = ctrls;
+ }
+
+ return LDAP_SUCCESS;
+
+}
diff --git a/ldap/servers/plugins/chainingdb/cb_debug.c b/ldap/servers/plugins/chainingdb/cb_debug.c
new file mode 100644
index 00000000..8a33e440
--- /dev/null
+++ b/ldap/servers/plugins/chainingdb/cb_debug.c
@@ -0,0 +1,23 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/*
+ * cb_debug.c - debugging-related code for Chaining backend
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include "cb.h"
+
+#ifdef _WIN32
+int *module_ldap_debug = 0;
+
+void plugin_init_debug_level(int *level_ptr)
+{
+ module_ldap_debug = level_ptr;
+}
+#endif
+
diff --git a/ldap/servers/plugins/chainingdb/cb_delete.c b/ldap/servers/plugins/chainingdb/cb_delete.c
new file mode 100644
index 00000000..f7eb77f6
--- /dev/null
+++ b/ldap/servers/plugins/chainingdb/cb_delete.c
@@ -0,0 +1,203 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include "cb.h"
+
+/*
+ * Perform an delete operation
+ *
+ * Returns:
+ * 0 - success
+ * <0 - fail
+ *
+ */
+
+int
+chaining_back_delete ( Slapi_PBlock *pb )
+{
+
+ Slapi_Backend * be;
+ cb_backend_instance *cb;
+ LDAPControl **ctrls, **serverctrls;
+ int rc,parse_rc,msgid,i;
+ LDAP *ld=NULL;
+ char **referrals=NULL;
+ LDAPMessage * res;
+ char *dn,* matched_msg, *error_msg;
+ char *cnxerrbuf=NULL;
+ time_t endtime;
+ cb_outgoing_conn *cnx;
+
+ if ( LDAP_SUCCESS != (rc=cb_forward_operation(pb) )) {
+ cb_send_ldap_result( pb, rc, NULL, "Chaining forbidden", 0, NULL );
+ return -1;
+ }
+
+ slapi_pblock_get( pb, SLAPI_BACKEND, &be );
+ cb = cb_get_instance(be);
+
+ cb_update_monitor_info(pb,cb,SLAPI_OPERATION_DELETE);
+
+ /* Check wether the chaining BE is available or not */
+ if ( cb_check_availability( cb, pb ) == FARMSERVER_UNAVAILABLE ){
+ return -1;
+ }
+
+ slapi_pblock_get( pb, SLAPI_DELETE_TARGET, &dn );
+
+ /*
+ * Check local acls
+ */
+
+ if (cb->local_acl && !cb->associated_be_is_disabled) {
+ char * errbuf=NULL;
+ Slapi_Entry *te = slapi_entry_alloc();
+ slapi_entry_set_dn(te,slapi_ch_strdup(dn));
+ rc = cb_access_allowed (pb, te, NULL, NULL, SLAPI_ACL_DELETE,&errbuf);
+ slapi_entry_free(te);
+
+ if ( rc != LDAP_SUCCESS ) {
+ cb_send_ldap_result( pb, rc, NULL, errbuf, 0, NULL );
+ slapi_ch_free((void **)&errbuf);
+ return -1;
+ }
+ }
+
+ /*
+ * Grab a connection handle
+ */
+
+ if ((rc = cb_get_connection(cb->pool,&ld,&cnx,NULL,&cnxerrbuf)) != LDAP_SUCCESS) {
+ cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL, cnxerrbuf, 0, NULL);
+ slapi_ch_free((void **)&cnxerrbuf);
+ /* ping the farm. If the farm is unreachable, we increment the counter */
+ cb_ping_farm(cb,NULL,0);
+ return -1;
+ }
+
+ /*
+ * Control management
+ */
+
+ if ( (rc = cb_update_controls( pb,ld,&ctrls,CB_UPDATE_CONTROLS_ADDAUTH )) != LDAP_SUCCESS ) {
+ cb_send_ldap_result( pb, rc, NULL,NULL, 0, NULL);
+ cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc));
+ return -1;
+ }
+
+ if ( slapi_op_abandoned( pb )) {
+ cb_release_op_connection(cb->pool,ld,0);
+ if ( NULL != ctrls)
+ ldap_controls_free(ctrls);
+ return -1;
+ }
+
+ /* heart-beat management */
+ if (cb->max_idle_time>0)
+ endtime=current_time() + cb->max_idle_time;
+
+ /*
+ * Send LDAP operation to the remote host
+ */
+
+ rc = ldap_delete_ext( ld, dn, ctrls, NULL, &msgid );
+ if ( NULL != ctrls)
+ ldap_controls_free(ctrls);
+ if ( rc != LDAP_SUCCESS ) {
+
+ cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL,
+ ldap_err2string(rc), 0, NULL);
+ cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc));
+ return -1;
+ }
+
+ while ( 1 ) {
+
+ if (cb_check_forward_abandon(cb,pb,ld,msgid)) {
+ return -1;
+ }
+
+ rc = ldap_result( ld, msgid, 0, &cb->abandon_timeout, &res );
+ switch ( rc ) {
+ case -1:
+ cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL,
+ ldap_err2string(rc), 0, NULL);
+ cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc));
+ if (res)
+ ldap_msgfree(res);
+ return -1;
+ case 0:
+ if ((rc=cb_ping_farm(cb,cnx,endtime)) != LDAP_SUCCESS) {
+
+ /* does not respond. give up and return a*/
+ /* error to the client. */
+
+ /*cb_send_ldap_result(pb,LDAP_OPERATIONS_ERROR, NULL,
+ ldap_err2string(rc), 0, NULL);*/
+ cb_send_ldap_result(pb,LDAP_OPERATIONS_ERROR, NULL, "FARM SERVER TEMPORARY UNAVAILABLE", 0, NULL);
+ cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc));
+ if (res)
+ ldap_msgfree(res);
+ return -1;
+ }
+#ifdef CB_YIELD
+ DS_Sleep(PR_INTERVAL_NO_WAIT);
+#endif
+ break;
+ default:
+ matched_msg=error_msg=NULL;
+ parse_rc = ldap_parse_result( ld, res, &rc, &matched_msg,
+ &error_msg, &referrals, &serverctrls, 1 );
+ if ( parse_rc != LDAP_SUCCESS ) {
+ cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL,
+ ldap_err2string(parse_rc), 0, NULL);
+ cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(parse_rc));
+ slapi_ch_free((void **)&matched_msg);
+ slapi_ch_free((void **)&error_msg);
+ if (serverctrls)
+ ldap_controls_free(serverctrls);
+ /* jarnou: free referrals */
+ if (referrals)
+ charray_free(referrals);
+ return -1;
+ }
+
+ if ( rc != LDAP_SUCCESS ) {
+ struct berval ** refs = referrals2berval(referrals);
+
+ cb_send_ldap_result( pb, rc, matched_msg, error_msg, 0, refs);
+ cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc));
+ slapi_ch_free((void **)&matched_msg);
+ slapi_ch_free((void **)&error_msg);
+ if (refs)
+ ber_bvecfree(refs);
+ if (referrals)
+ charray_free(referrals);
+ if (serverctrls)
+ ldap_controls_free(serverctrls);
+ return -1;
+ }
+
+ cb_release_op_connection(cb->pool,ld,0);
+
+ /* Add control response sent by the farm server */
+
+ for (i=0; serverctrls && serverctrls[i];i++)
+ slapi_pblock_set( pb, SLAPI_ADD_RESCONTROL, serverctrls[i]);
+ if (serverctrls)
+ ldap_controls_free(serverctrls);
+ /* jarnou: free matched_msg, error_msg, and referrals if necessary */
+ slapi_ch_free((void **)&matched_msg);
+ slapi_ch_free((void **)&error_msg);
+ if (referrals)
+ charray_free(referrals);
+ cb_send_ldap_result( pb, LDAP_SUCCESS, NULL, NULL, 0, NULL );
+ return 0;
+ }
+ }
+
+ /* Never reached */
+ /* return 0; */
+}
diff --git a/ldap/servers/plugins/chainingdb/cb_init.c b/ldap/servers/plugins/chainingdb/cb_init.c
new file mode 100644
index 00000000..70af3d42
--- /dev/null
+++ b/ldap/servers/plugins/chainingdb/cb_init.c
@@ -0,0 +1,120 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include "cb.h"
+
+Slapi_PluginDesc chainingdbdesc = { CB_PLUGIN_NAME,
+ PLUGIN_MAGIC_VENDOR_STR,
+ PRODUCTTEXT,
+ CB_PLUGIN_DESCRIPTION };
+
+
+static cb_backend * cb_backend_type=NULL;
+
+cb_backend * cb_get_backend_type() {
+ return cb_backend_type;
+}
+
+static void cb_set_backend_type(cb_backend * cb) {
+ cb_backend_type=cb;
+}
+
+/* Initialization function */
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+
+int
+chaining_back_init( Slapi_PBlock *pb )
+{
+
+ int rc=0;
+ cb_backend *cb;
+ struct slapdplugin *p;
+
+ cb = (cb_backend *) slapi_ch_calloc( 1, sizeof(cb_backend));
+
+ /* Record the identity of the chaining plugin. used during internal ops.*/
+ slapi_pblock_get(pb, SLAPI_PLUGIN_IDENTITY, &(cb->identity));
+
+ /* keep a pointer back to the plugin */
+ slapi_pblock_get(pb, SLAPI_PLUGIN, &p);
+ cb->plugin = p;
+
+ /* Initialize misc. fields */
+ cb->config.rwl_config_lock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "chaining_db");
+ rc = slapi_pblock_set( pb, SLAPI_PLUGIN_PRIVATE, (void *) cb );
+ cb->pluginDN=slapi_ch_calloc( 1,strlen(PLUGIN_BASE_DN)+strlen(CB_PLUGIN_NAME)+5);
+ sprintf(cb->pluginDN,"cn=%s,%s",CB_PLUGIN_NAME,PLUGIN_BASE_DN);
+
+ cb->configDN=slapi_ch_calloc( 1,strlen(cb->pluginDN)+11);
+ sprintf(cb->configDN,"cn=config,%s",cb->pluginDN);
+
+ /* Set backend callback functions */
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_03 );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&chainingdbdesc );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DB_SEARCH_FN,
+ (void *) chainingdb_build_candidate_list );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DB_NEXT_SEARCH_ENTRY_FN,
+ (void *) chainingdb_next_search_entry );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_START_FN,
+ (void *) chainingdb_start ) ;
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DB_BIND_FN,
+ (void *) chainingdb_bind ) ;
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DB_ADD_FN,
+ (void *) chaining_back_add );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DB_DELETE_FN,
+ (void *) chaining_back_delete );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DB_COMPARE_FN,
+ (void *) chaining_back_compare );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DB_MODIFY_FN,
+ (void *) chaining_back_modify );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DB_MODRDN_FN,
+ (void *) chaining_back_modrdn );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DB_ABANDON_FN,
+ (void *) chaining_back_abandon );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DB_SIZE_FN,
+ (void *) cb_db_size );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_CLOSE_FN,
+ (void *) cb_back_close );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_CLEANUP_FN,
+ (void *) cb_back_cleanup );
+
+/****
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DB_ENTRY_RELEASE_FN,
+ (void *) chaining_back_entry_release );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DB_INIT_INSTANCE_FN,
+ (void *) chaining_back_init);
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DB_TEST_FN,
+ (void *) cb_back_test );
+****/
+
+ /*
+ ** The following callbacks are not implemented
+ ** by the chaining backend
+ ** - SLAPI_PLUGIN_DB_FLUSH_FN
+ ** - SLAPI_PLUGIN_DB_SEQ_FN
+ ** - SLAPI_PLUGIN_DB_RMDB_FN
+ ** - SLAPI_PLUGIN_DB_DB2INDEX_FN
+ ** - SLAPI_PLUGIN_DB_LDIF2DB_FN
+ ** - SLAPI_PLUGIN_DB_DB2LDIF_FN
+ ** - SLAPI_PLUGIN_DB_ARCHIVE2DB_FN
+ ** - SLAPI_PLUGIN_DB_DB2ARCHIVE_FN
+ ** - SLAPI_PLUGIN_DB_BEGIN_FN
+ ** - SLAPI_PLUGIN_DB_COMMIT_FN
+ ** - SLAPI_PLUGIN_DB_ABORT_FN
+ ** - SLAPI_PLUGIN_DB_NEXT_SEARCH_ENTRY_EXT_FN
+ */
+
+ if ( rc != 0 ) {
+ slapi_log_error( SLAPI_LOG_FATAL, CB_PLUGIN_SUBSYSTEM, "chaining_back_init failed\n");
+ return( -1 );
+ }
+
+ cb_set_backend_type(cb);
+
+ return (0);
+}
+
diff --git a/ldap/servers/plugins/chainingdb/cb_instance.c b/ldap/servers/plugins/chainingdb/cb_instance.c
new file mode 100644
index 00000000..60af726f
--- /dev/null
+++ b/ldap/servers/plugins/chainingdb/cb_instance.c
@@ -0,0 +1,1871 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include "cb.h"
+
+/*
+** 1 set/get function for each parameter of a backend instance
+** NOTE: Some parameters won't be taken into account until the server has restarted
+** In such cases, the internal conf is not updated but the new value is stored in the
+** dse.ldif file.
+**/
+
+/* Get functions */
+
+static void *cb_instance_hosturl_get(void *arg);
+static void *cb_instance_binduser_get(void *arg);
+static void *cb_instance_userpassword_get(void *arg);
+static void *cb_instance_maxbconn_get(void *arg);
+static void *cb_instance_maxconn_get(void *arg);
+static void *cb_instance_abandonto_get(void *arg);
+static void *cb_instance_maxbconc_get(void *arg);
+static void *cb_instance_maxconc_get(void *arg);
+static void *cb_instance_imperson_get(void *arg);
+static void *cb_instance_connlife_get(void *arg);
+static void *cb_instance_bindto_get(void *arg);
+static void *cb_instance_opto_get(void *arg);
+static void *cb_instance_ref_get(void *arg);
+static void *cb_instance_acl_get(void *arg);
+static void *cb_instance_bindretry_get(void *arg);
+static void *cb_instance_sizelimit_get(void *arg);
+static void *cb_instance_timelimit_get(void *arg);
+static void *cb_instance_hoplimit_get(void *arg);
+static void *cb_instance_max_idle_get(void *arg);
+static void *cb_instance_max_test_get(void *arg);
+
+
+/* Set functions */
+
+static int cb_instance_hosturl_set(void *arg, void *value, char *errorbuf, int phase, int apply);
+static int cb_instance_binduser_set(void *arg, void *value, char *errorbuf, int phase, int apply);
+static int cb_instance_userpassword_set(void *arg, void *value, char *errorbuf, int phase, int apply);
+static int cb_instance_maxbconn_set(void *arg, void *value, char *errorbuf, int phase, int apply);
+static int cb_instance_maxconn_set(void *arg, void *value, char *errorbuf, int phase, int apply);
+static int cb_instance_abandonto_set(void *arg, void *value, char *errorbuf, int phase, int apply);
+static int cb_instance_maxbconc_set(void *arg, void *value, char *errorbuf, int phase, int apply);
+static int cb_instance_maxconc_set(void *arg, void *value, char *errorbuf, int phase, int apply);
+static int cb_instance_imperson_set(void *arg, void *value, char *errorbuf, int phase, int apply);
+static int cb_instance_connlife_set(void *arg, void *value, char *errorbuf, int phase, int apply);
+static int cb_instance_bindto_set(void *arg, void *value, char *errorbuf, int phase, int apply);
+static int cb_instance_opto_set(void *arg, void *value, char *errorbuf, int phase, int apply);
+static int cb_instance_ref_set(void *arg, void *value, char *errorbuf, int phase, int apply);
+static int cb_instance_acl_set(void *arg, void *value, char *errorbuf, int phase, int apply);
+static int cb_instance_bindretry_set(void *arg, void *value, char *errorbuf, int phase, int apply);
+static int cb_instance_sizelimit_set(void *arg, void *value, char *errorbuf, int phase, int apply);
+static int cb_instance_timelimit_set(void *arg, void *value, char *errorbuf, int phase, int apply);
+static int cb_instance_hoplimit_set(void *arg, void *value, char *errorbuf, int phase, int apply);
+static int cb_instance_max_idle_set(void *arg, void *value, char *errorbuf, int phase, int apply);
+static int cb_instance_max_test_set(void *arg, void *value, char *errorbuf, int phase, int apply);
+
+/* Default hardwired values */
+
+cb_instance_config_info cb_the_instance_config[] = {
+{CB_CONFIG_HOSTURL,CB_CONFIG_TYPE_STRING,"",&cb_instance_hosturl_get,&cb_instance_hosturl_set,CB_ALWAYS_SHOW},
+{CB_CONFIG_BINDUSER, CB_CONFIG_TYPE_STRING, "", &cb_instance_binduser_get, &cb_instance_binduser_set,CB_ALWAYS_SHOW},
+{CB_CONFIG_USERPASSWORD,CB_CONFIG_TYPE_STRING,"",&cb_instance_userpassword_get,&cb_instance_userpassword_set,CB_ALWAYS_SHOW},
+{CB_CONFIG_MAXBINDCONNECTIONS,CB_CONFIG_TYPE_INT,CB_DEF_BIND_MAXCONNECTIONS,&cb_instance_maxbconn_get, &cb_instance_maxbconn_set,CB_ALWAYS_SHOW},
+{CB_CONFIG_MAXCONNECTIONS,CB_CONFIG_TYPE_INT,CB_DEF_MAXCONNECTIONS,&cb_instance_maxconn_get, &cb_instance_maxconn_set,CB_ALWAYS_SHOW},
+{CB_CONFIG_ABANDONTIMEOUT,CB_CONFIG_TYPE_INT,CB_DEF_ABANDON_TIMEOUT,&cb_instance_abandonto_get, &cb_instance_abandonto_set,CB_ALWAYS_SHOW},
+{CB_CONFIG_MAXBINDCONCURRENCY,CB_CONFIG_TYPE_INT,CB_DEF_BIND_MAXCONCURRENCY,&cb_instance_maxbconc_get, &cb_instance_maxbconc_set,CB_ALWAYS_SHOW},
+{CB_CONFIG_MAXCONCURRENCY,CB_CONFIG_TYPE_INT,CB_DEF_MAXCONCURRENCY,&cb_instance_maxconc_get, &cb_instance_maxconc_set,CB_ALWAYS_SHOW},
+{CB_CONFIG_IMPERSONATION,CB_CONFIG_TYPE_ONOFF,CB_DEF_IMPERSONATION,&cb_instance_imperson_get, &cb_instance_imperson_set,CB_ALWAYS_SHOW},
+{CB_CONFIG_CONNLIFETIME,CB_CONFIG_TYPE_INT,CB_DEF_CONNLIFETIME,&cb_instance_connlife_get, &cb_instance_connlife_set,CB_ALWAYS_SHOW},
+{CB_CONFIG_BINDTIMEOUT,CB_CONFIG_TYPE_INT,CB_DEF_BINDTIMEOUT,&cb_instance_bindto_get, &cb_instance_bindto_set,CB_ALWAYS_SHOW},
+{CB_CONFIG_TIMEOUT,CB_CONFIG_TYPE_INT,"0",&cb_instance_opto_get, &cb_instance_opto_set,0},
+{CB_CONFIG_REFERRAL,CB_CONFIG_TYPE_ONOFF,CB_DEF_SEARCHREFERRAL,&cb_instance_ref_get, &cb_instance_ref_set,CB_ALWAYS_SHOW},
+{CB_CONFIG_LOCALACL,CB_CONFIG_TYPE_ONOFF,CB_DEF_LOCALACL,&cb_instance_acl_get, &cb_instance_acl_set,CB_ALWAYS_SHOW},
+{CB_CONFIG_BINDRETRY,CB_CONFIG_TYPE_INT,CB_DEF_BINDRETRY,&cb_instance_bindretry_get, &cb_instance_bindretry_set,CB_ALWAYS_SHOW},
+{CB_CONFIG_SIZELIMIT,CB_CONFIG_TYPE_INT,CB_DEF_SIZELIMIT,&cb_instance_sizelimit_get, &cb_instance_sizelimit_set,CB_ALWAYS_SHOW},
+{CB_CONFIG_TIMELIMIT,CB_CONFIG_TYPE_INT,CB_DEF_TIMELIMIT,&cb_instance_timelimit_get, &cb_instance_timelimit_set,CB_ALWAYS_SHOW},
+{CB_CONFIG_HOPLIMIT,CB_CONFIG_TYPE_INT,CB_DEF_HOPLIMIT,&cb_instance_hoplimit_get, &cb_instance_hoplimit_set,CB_ALWAYS_SHOW},
+{CB_CONFIG_MAX_IDLE_TIME,CB_CONFIG_TYPE_INT,CB_DEF_MAX_IDLE_TIME,&cb_instance_max_idle_get, &cb_instance_max_idle_set,CB_ALWAYS_SHOW},
+{CB_CONFIG_MAX_TEST_TIME,CB_CONFIG_TYPE_INT,CB_DEF_MAX_TEST_TIME,&cb_instance_max_test_get, &cb_instance_max_test_set,CB_ALWAYS_SHOW},
+{NULL, 0, NULL, NULL, NULL, 0}
+};
+
+/* Others forward declarations */
+
+int cb_instance_delete_config_callback(Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* e2,
+ int *returncode, char *returntext, void *arg);
+int cb_instance_search_config_callback(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e,
+ int *returncode, char *returntext, void *arg);
+int cb_instance_add_config_callback(Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* e2,
+ int *returncode, char *returntext, void *arg);
+static int
+cb_instance_config_set(void *arg, char *attr_name, cb_instance_config_info *config_array,
+struct berval *bval, char *err_buf, int phase, int apply_mod);
+
+
+int
+cb_dont_allow_that(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e,
+ int *returncode, char *returntext, void *arg)
+{
+ *returncode=LDAP_UNWILLING_TO_PERFORM;
+ return SLAPI_DSE_CALLBACK_ERROR;
+}
+
+static char *cb_skeleton_entries[] =
+{
+
+ "dn:cn=monitor, cn=%s, cn=%s, cn=plugins, cn=config\n"
+ "objectclass:top\n"
+ "objectclass:extensibleObject\n"
+ "cn:monitor\n",
+
+ ""
+};
+
+/*
+** Initialize a backend instance with a default configuration
+*/
+
+static void cb_instance_config_set_default(cb_backend_instance *inst)
+{
+ cb_instance_config_info *config;
+ char err_buf[CB_BUFSIZE];
+
+ for (config = cb_the_instance_config; config->config_name != NULL; config++) {
+ cb_instance_config_set((void *)inst,
+ config->config_name, cb_the_instance_config, NULL /* use default */, err_buf,
+ CB_CONFIG_PHASE_INITIALIZATION, 1 /* apply */);
+ }
+
+ /* Set backend instance flags */
+ if (inst->inst_be)
+ slapi_be_set_flag(inst->inst_be,SLAPI_BE_FLAG_REMOTE_DATA);
+}
+
+/*
+** Allocate a new chaining backend instance. Internal structure
+*/
+
+static cb_backend_instance * cb_instance_alloc(cb_backend * cb, char * name, char * basedn) {
+
+ int i;
+
+ cb_backend_instance * inst =
+ (cb_backend_instance *)slapi_ch_calloc(1, sizeof(cb_backend_instance));
+
+ /* associated_be_is_disabled defaults to 0 - this may be a problem if the associated
+ be is disabled at instance creation time
+ */
+ inst->inst_name = slapi_ch_strdup(name);
+ inst->monitor.mutex = slapi_new_mutex();
+ inst->monitor_availability.cpt_lock = slapi_new_mutex();
+ inst->monitor_availability.lock_timeLimit = slapi_new_mutex();
+ inst->pool= (cb_conn_pool *) slapi_ch_calloc(1,sizeof(cb_conn_pool));
+ inst->pool->conn.conn_list_mutex = slapi_new_mutex();
+ inst->pool->conn.conn_list_cv = slapi_new_condvar(inst->pool->conn.conn_list_mutex);
+ inst->pool->bindit=1;
+
+ inst->bind_pool= (cb_conn_pool *) slapi_ch_calloc(1,sizeof(cb_conn_pool));
+ inst->bind_pool->conn.conn_list_mutex = slapi_new_mutex();
+ inst->bind_pool->conn.conn_list_cv = slapi_new_condvar(inst->bind_pool->conn.conn_list_mutex);
+
+ inst->backend_type=cb;
+ /* initialize monitor_availability */
+ inst->monitor_availability.farmserver_state = FARMSERVER_AVAILABLE ; /* we expect the farm to be available */
+ inst->monitor_availability.cpt = 0 ; /* set up the failed conn counter to 0 */
+
+ /* create RW lock to protect the config */
+ inst->rwl_config_lock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, slapi_ch_strdup(name));
+
+ /* quick hack */
+ /* put a ref to the config lock in the pool */
+ /* so that the connection mgmt module can */
+ /* access the config safely */
+
+ inst->pool->rwl_config_lock = inst->rwl_config_lock;
+ inst->bind_pool->rwl_config_lock = inst->rwl_config_lock;
+
+ for (i=0; i < MAX_CONN_ARRAY; i++) {
+ inst->pool->connarray[i] = NULL;
+ inst->bind_pool->connarray[i] = NULL;
+ }
+
+ /* Config is now merged with the backend entry */
+ inst->configDn=slapi_ch_strdup(basedn);
+
+ inst->monitorDn=(char *) slapi_ch_calloc(1,strlen(basedn)+15);
+ sprintf(inst->monitorDn,"cn=monitor,%s",basedn);
+
+ inst->eq_ctx = NULL;
+
+ return inst;
+}
+
+void cb_instance_free(cb_backend_instance * inst) {
+
+ if (inst) {
+ PR_RWLock_Wlock(inst->rwl_config_lock);
+
+ if ( inst->eq_ctx != NULL )
+ {
+ slapi_eq_cancel(inst->eq_ctx);
+ inst->eq_ctx = NULL;
+ }
+
+ if (inst->bind_pool)
+ cb_close_conn_pool(inst->bind_pool);
+ if (inst->pool)
+ cb_close_conn_pool(inst->pool);
+
+ slapi_destroy_condvar(inst->bind_pool->conn.conn_list_cv);
+ slapi_destroy_condvar(inst->pool->conn.conn_list_cv);
+ slapi_destroy_mutex(inst->monitor.mutex);
+ slapi_destroy_mutex(inst->bind_pool->conn.conn_list_mutex);
+ slapi_destroy_mutex(inst->pool->conn.conn_list_mutex);
+ slapi_destroy_mutex(inst->monitor_availability.cpt_lock);
+ slapi_destroy_mutex(inst->monitor_availability.lock_timeLimit);
+ slapi_ch_free((void **) &inst->configDn);
+ slapi_ch_free((void **) &inst->monitorDn);
+ slapi_ch_free((void **) &inst->inst_name);
+ charray_free(inst->every_attribute);
+
+ slapi_ch_free((void **) &inst->bind_pool);
+ slapi_ch_free((void **) &inst->pool);
+
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+
+ PR_DestroyRWLock(inst->rwl_config_lock);
+
+ slapi_ch_free((void **) &inst);
+ }
+/* XXXSD */
+}
+
+/*
+** Check the change the configuration of an existing chaining
+** backend instance.
+*/
+
+int cb_instance_modify_config_check_callback(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e,
+ int *returncode, char *returntext, void *arg) {
+
+ cb_backend_instance * inst = (cb_backend_instance *) arg;
+ LDAPMod **mods;
+ int rc = LDAP_SUCCESS;
+ int i;
+ char * attr_name;
+ returntext[0] = '\0';
+
+ CB_ASSERT(inst!=NULL);
+ slapi_pblock_get( pb, SLAPI_MODIFY_MODS, &mods );
+
+ /* First pass to validate input */
+ for (i = 0; mods[i] && LDAP_SUCCESS == rc; i++) {
+ attr_name = mods[i]->mod_type;
+
+ /* specific processing for multi-valued attributes */
+ if ( !strcasecmp ( attr_name, CB_CONFIG_SUFFIX )) {
+ sprintf(returntext, "suffix modification not allowed\n");
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ continue;
+ } else
+ if ( !strcasecmp ( attr_name, CB_CONFIG_ILLEGAL_ATTRS )) {
+ continue;
+ } else
+ if ( !strcasecmp ( attr_name, CB_CONFIG_CHAINING_COMPONENTS )) {
+ continue;
+ } else
+
+ /* CB_CONFIG_BINDUSER & CB_CONFIG_USERPASSWORD may be added */
+ /* or deleted */
+
+ if ( !strcasecmp ( attr_name, CB_CONFIG_USERPASSWORD )) {
+ continue;
+ } else
+ if ( !strcasecmp ( attr_name, CB_CONFIG_BINDUSER )) {
+
+ /* Make sure value is not forbidden */
+ if ((mods[i]->mod_op & LDAP_MOD_REPLACE) ||
+ ((mods[i]->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_ADD)) {
+
+ rc = cb_instance_config_set((void *) inst, attr_name,
+ cb_the_instance_config, mods[i]->mod_bvalues[0], returntext,
+ CB_CONFIG_PHASE_RUNNING, 0);
+ continue;
+ }
+ }
+
+ if ((mods[i]->mod_op & LDAP_MOD_DELETE) ||
+ ((mods[i]->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_ADD)) {
+ rc= LDAP_UNWILLING_TO_PERFORM;
+ sprintf(returntext, "%s attributes is not allowed",
+ (mods[i]->mod_op & LDAP_MOD_DELETE) ? "Deleting" : "Adding");
+ } else if (mods[i]->mod_op & LDAP_MOD_REPLACE) {
+ /* This assumes there is only one bval for this mod. */
+ rc = cb_instance_config_set((void *) inst, attr_name,
+ cb_the_instance_config, mods[i]->mod_bvalues[0], returntext,
+ CB_CONFIG_PHASE_RUNNING, 0);
+ }
+ }
+ *returncode= rc;
+ return ((LDAP_SUCCESS == rc) ? 1:-1);
+}
+
+
+/*
+** Change the configuration of an existing chaining
+** backend instance.
+*/
+
+int cb_instance_modify_config_callback(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e,
+ int *returncode, char *returntext, void *arg) {
+
+ cb_backend_instance * inst = (cb_backend_instance *) arg;
+ LDAPMod **mods;
+ int rc = LDAP_SUCCESS;
+ int i;
+ int reopen_conn=0;
+ char * attr_name;
+ returntext[0] = '\0';
+
+ CB_ASSERT(inst!=NULL);
+ slapi_pblock_get( pb, SLAPI_MODIFY_MODS, &mods );
+
+ /* input checked in the preop modify callback */
+
+ for (i = 0; mods[i] && LDAP_SUCCESS == rc; i++) {
+ attr_name = mods[i]->mod_type;
+
+ /* specific processing for multi-valued attributes */
+ if ( !strcasecmp ( attr_name, CB_CONFIG_ILLEGAL_ATTRS )) {
+ char * config_attr_value;
+ int done=0;
+ int j;
+
+ PR_RWLock_Wlock(inst->rwl_config_lock);
+ for (j = 0; mods[i]->mod_bvalues && mods[i]->mod_bvalues[j]; j++) {
+ config_attr_value = (char *) mods[i]->mod_bvalues[j]->bv_val;
+ if ( mods[i]->mod_op & LDAP_MOD_REPLACE) {
+ if (!done) {
+ charray_free(inst->illegal_attributes);
+ inst->illegal_attributes=NULL;
+ done=1;
+ }
+ charray_add(&inst->illegal_attributes,
+ slapi_ch_strdup(config_attr_value));
+ } else
+ if ( (mods[i]->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_ADD) {
+ charray_add(&inst->illegal_attributes,
+ slapi_ch_strdup(config_attr_value));
+ } else
+ if ( (mods[i]->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_DELETE) {
+ charray_remove(inst->illegal_attributes,
+ slapi_ch_strdup(config_attr_value));
+ }
+ }
+ if (NULL == mods[i]->mod_bvalues) {
+ charray_free(inst->illegal_attributes);
+ inst->illegal_attributes=NULL;
+ }
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ continue;
+ }
+ if ( !strcasecmp ( attr_name, CB_CONFIG_CHAINING_COMPONENTS )) {
+ char * config_attr_value;
+ int done=0;
+ int j;
+
+ PR_RWLock_Wlock(inst->rwl_config_lock);
+ for (j = 0; mods[i]->mod_bvalues && mods[i]->mod_bvalues[j]; j++) {
+ config_attr_value = (char *) mods[i]->mod_bvalues[j]->bv_val;
+ if ( mods[i]->mod_op & LDAP_MOD_REPLACE) {
+ if (!done) {
+ charray_free(inst->chaining_components);
+ inst->chaining_components=NULL;
+ done=1;
+ }
+ /* XXXSD assume dns */
+ charray_add(&inst->chaining_components,
+ slapi_dn_normalize(slapi_ch_strdup(config_attr_value)));
+ } else
+ if ( (mods[i]->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_ADD) {
+ charray_add(&inst->chaining_components,
+ slapi_dn_normalize(slapi_ch_strdup(config_attr_value)));
+ } else
+ if ( (mods[i]->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_DELETE) {
+ charray_remove(inst->chaining_components,
+ slapi_dn_normalize(slapi_ch_strdup(config_attr_value)));
+ }
+ }
+ if (NULL == mods[i]->mod_bvalues) {
+ charray_free(inst->chaining_components);
+ inst->chaining_components=NULL;
+ }
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ continue;
+ }
+
+
+
+ if ((mods[i]->mod_op & LDAP_MOD_DELETE) ||
+ ((mods[i]->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_ADD)) {
+
+ /* Special processing for binddn & password */
+ /* because they are optional */
+
+ if (( !strcasecmp ( attr_name, CB_CONFIG_BINDUSER )) ||
+ ( !strcasecmp ( attr_name, CB_CONFIG_USERPASSWORD ))) {
+
+ if (mods[i]->mod_op & LDAP_MOD_DELETE) {
+ rc = cb_instance_config_set((void *) inst, attr_name,
+ cb_the_instance_config, NULL, returntext,
+ CB_CONFIG_PHASE_RUNNING, 1); }
+ else {
+ rc = cb_instance_config_set((void *) inst, attr_name,
+ cb_the_instance_config, mods[i]->mod_bvalues[0], returntext,
+ CB_CONFIG_PHASE_RUNNING, 1);
+ }
+ if (rc==CB_REOPEN_CONN) {
+ reopen_conn=1;
+ rc=LDAP_SUCCESS;
+ }
+ continue;
+ }
+
+ rc= LDAP_UNWILLING_TO_PERFORM;
+ sprintf(returntext, "%s attributes is not allowed",
+ (mods[i]->mod_op & LDAP_MOD_DELETE) ? "Deleting" : "Adding");
+ } else if (mods[i]->mod_op & LDAP_MOD_REPLACE) {
+ /* This assumes there is only one bval for this mod. */
+ rc = cb_instance_config_set((void *) inst, attr_name,
+ cb_the_instance_config, mods[i]->mod_bvalues[0], returntext,
+ CB_CONFIG_PHASE_RUNNING, 1);
+
+ /* Update requires to reopen connections */
+ /* Expensive operation so do it only once */
+ if (rc==CB_REOPEN_CONN) {
+ reopen_conn=1;
+ rc=LDAP_SUCCESS;
+ }
+ }
+ }
+
+ *returncode= rc;
+
+ if (reopen_conn) {
+ cb_stale_all_connections(inst);
+ }
+
+ return ((LDAP_SUCCESS == rc) ? SLAPI_DSE_CALLBACK_OK:SLAPI_DSE_CALLBACK_ERROR);
+}
+
+/*
+** Parse and instantiate backend instances
+*/
+
+int
+cb_parse_instance_config_entry(cb_backend * cb, Slapi_Entry * e) {
+
+ int rc =LDAP_SUCCESS;
+ Slapi_Attr *attr = NULL;
+ Slapi_Value *sval;
+ const struct berval *attrValue;
+ cb_backend_instance *inst=NULL;
+ char *instname;
+ Slapi_PBlock *search_pb=NULL;
+ char retmsg[CB_BUFSIZE];
+
+ CB_ASSERT(e!=NULL);
+
+ /*
+ ** Retrieve the instance name and make sure it is not
+ ** already declared.
+ */
+
+ if ( 0 == slapi_entry_attr_find( e, CB_CONFIG_INSTNAME, &attr )) {
+ slapi_attr_first_value(attr, &sval);
+ attrValue = slapi_value_get_berval(sval);
+ instname=attrValue->bv_val;
+ } else {
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "Malformed backend instance (<%s> missing)>\n", CB_CONFIG_INSTNAME);
+ return LDAP_LOCAL_ERROR;
+ }
+
+ /* Allocate a new backend internal data structure */
+ inst = cb_instance_alloc(cb,instname,slapi_entry_get_dn(e));
+
+ /* Emulate a add config entry to configure */
+ /* this backend instance. */
+
+ cb_instance_add_config_callback(NULL,e,NULL,&rc,retmsg,inst);
+ if ( rc != LDAP_SUCCESS ) {
+ cb_instance_free(inst);
+ }
+ return rc;
+}
+
+/*
+** Update the instance configuration
+*/
+
+static int
+cb_instance_config_initialize(cb_backend_instance * inst, Slapi_Entry * e , int phase, int apply) {
+
+ int rc =LDAP_SUCCESS;
+ Slapi_Attr *attr = NULL;
+ Slapi_Value *sval;
+ struct berval * bval;
+ int using_def_connlifetime,i;
+ char err_buf[CB_BUFSIZE];
+ int urlfound=0;
+ char *rootdn;
+
+ using_def_connlifetime=1;
+
+ for (slapi_entry_first_attr(e, &attr); attr; slapi_entry_next_attr(e, attr, &attr)) {
+ char * attr_name=NULL;
+ slapi_attr_get_type(attr, &attr_name);
+
+ if ( !strcasecmp ( attr_name, CB_CONFIG_SUFFIX )) {
+ if (apply && ( inst->inst_be != NULL )) {
+ Slapi_DN *suffix;
+ suffix = slapi_sdn_new();
+ i = slapi_attr_first_value(attr, &sval);
+ while (i != -1 ) {
+ bval = (struct berval *) slapi_value_get_berval(sval);
+ slapi_sdn_init_dn_byref(suffix, bval->bv_val);
+
+ if (!slapi_be_issuffix(inst->inst_be, suffix)) {
+ slapi_be_addsuffix(inst->inst_be, suffix);
+ }
+ slapi_sdn_done(suffix);
+ slapi_sdn_free(&suffix);
+ i = slapi_attr_next_value(attr, i, &sval);
+ }
+ }
+ continue;
+ } else
+ if ( !strcasecmp ( attr_name, CB_CONFIG_CHAINING_COMPONENTS )) {
+
+ if (apply) {
+ PR_RWLock_Wlock(inst->rwl_config_lock);
+ i = slapi_attr_first_value(attr, &sval);
+ charray_free(inst->chaining_components);
+ inst->chaining_components=NULL;
+ while (i != -1 ) {
+ bval = (struct berval *) slapi_value_get_berval(sval);
+ charray_add(&inst->chaining_components,
+ slapi_dn_normalize(slapi_ch_strdup(bval->bv_val)));
+ i = slapi_attr_next_value(attr, i, &sval);
+ }
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ }
+ continue;
+ } else
+ if ( !strcasecmp ( attr_name, CB_CONFIG_ILLEGAL_ATTRS )) {
+
+ if (apply) {
+ PR_RWLock_Wlock(inst->rwl_config_lock);
+ i = slapi_attr_first_value(attr, &sval);
+ charray_free(inst->illegal_attributes);
+ inst->illegal_attributes=NULL;
+ while (i != -1 ) {
+ bval = (struct berval *) slapi_value_get_berval(sval);
+ charray_add(&inst->illegal_attributes,
+ slapi_ch_strdup(bval->bv_val));
+ i = slapi_attr_next_value(attr, i, &sval);
+ }
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ }
+ continue;
+ }
+
+
+ if ( !strcasecmp ( attr_name, CB_CONFIG_HOSTURL )) {
+ urlfound=1;
+ }
+
+
+ /* We are assuming that each of these attributes are to have
+ * only one value. If they have more than one value, like
+ * the nsslapd-suffix attribute, then they need to be
+ * handled differently. */
+
+ slapi_attr_first_value(attr, &sval);
+ bval = (struct berval *) slapi_value_get_berval(sval);
+
+ if (cb_instance_config_set((void *) inst, attr_name,
+ cb_the_instance_config, bval, err_buf, phase, apply ) != LDAP_SUCCESS) {
+ slapi_log_error( SLAPI_LOG_FATAL,
+ CB_PLUGIN_SUBSYSTEM,"Error with config attribute %s : %s\n",
+ attr_name, err_buf);
+ rc=LDAP_LOCAL_ERROR;
+ break;
+ }
+ if ( !strcasecmp ( attr_name, CB_CONFIG_CONNLIFETIME )) {
+ using_def_connlifetime=0;
+ }
+ }
+
+
+ /*
+ ** Check for mandatory attributes
+ ** Post-Processing
+ */
+
+ if (LDAP_SUCCESS == rc) {
+ if (!urlfound) {
+ slapi_log_error( SLAPI_LOG_FATAL, CB_PLUGIN_SUBSYSTEM,
+ "Malformed backend instance entry. Mandatory attr <%s> missing\n",
+ CB_CONFIG_HOSTURL);
+ rc= LDAP_LOCAL_ERROR;
+ }
+
+ if (apply ) {
+ if ( using_def_connlifetime &&
+ strchr( inst->pool->hostname, ' ' ) != NULL ) {
+
+ cb_instance_config_set((void *)inst, CB_CONFIG_CONNLIFETIME,
+ cb_the_instance_config, NULL /* use default */, err_buf,
+ CB_CONFIG_PHASE_INITIALIZATION, 1 );
+ }
+ }
+ }
+
+ /*
+ ** Additional checks
+ ** It is forbidden to use directory manager as proxy user
+ ** due to a bug in the acl check
+ */
+
+ rootdn=cb_get_rootdn();
+
+ if (inst->impersonate && inst->pool && inst->pool->binddn &&
+ !strcmp(inst->pool->binddn,rootdn)) { /* UTF8 aware */
+ slapi_log_error( SLAPI_LOG_FATAL,
+ CB_PLUGIN_SUBSYSTEM,"Error with config attribute %s (%s: forbidden value)\n",
+ CB_CONFIG_BINDUSER, rootdn);
+ rc=LDAP_LOCAL_ERROR;
+ }
+ slapi_ch_free((void **)&rootdn);
+
+ return rc;
+}
+
+/********************************************************
+** Get and set functions for chaining backend instances *
+*********************************************************
+*/
+
+static void *cb_instance_hosturl_get(void *arg)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+ char * data;
+
+ PR_RWLock_Rlock(inst->rwl_config_lock);
+ data = slapi_ch_strdup(inst->pool->url);
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ return data;
+}
+
+static int cb_instance_hosturl_set(void *arg, void *value, char *errorbuf, int phase, int apply)
+{
+ cb_backend_instance *inst=(cb_backend_instance *) arg;
+ char *url = (char *) value;
+ LDAPURLDesc *ludp=NULL;
+ int rc=LDAP_SUCCESS;
+
+ if (( rc = ldap_url_parse( url, &ludp )) != 0 ) {
+ strcpy(errorbuf,cb_urlparse_err2string( rc ));
+ if (CB_CONFIG_PHASE_INITIALIZATION == phase)
+ inst->pool->url=slapi_ch_strdup("");
+ return(LDAP_INVALID_SYNTAX);
+ }
+
+ if (apply) {
+
+ char * ptr;
+
+ PR_RWLock_Wlock(inst->rwl_config_lock);
+
+ if (( phase != CB_CONFIG_PHASE_INITIALIZATION ) &&
+ ( phase != CB_CONFIG_PHASE_STARTUP )) {
+
+ /* Dynamic modification */
+ /* Don't free char * pointer now */
+ /* STore them in a waste basket */
+ /* Will be relesase when the backend stops */
+
+ if (inst->pool->hostname)
+ charray_add(&inst->pool->waste_basket,inst->pool->hostname);
+ if (inst->pool->url)
+ charray_add(&inst->pool->waste_basket,inst->pool->url);
+
+ if (inst->bind_pool->hostname)
+ charray_add(&inst->bind_pool->waste_basket,inst->bind_pool->hostname);
+ if (inst->bind_pool->url)
+ charray_add(&inst->bind_pool->waste_basket,inst->bind_pool->url);
+
+ /* Require connection cleanup */
+ rc=CB_REOPEN_CONN;
+ }
+
+ /* Normal case. Extract useful data from */
+ /* the url and update the configuration */
+
+ if ((ludp->lud_host==NULL) || (strlen(ludp->lud_host)==0)) {
+ inst->pool->hostname=(char *)slapi_ch_strdup((char *)get_localhost_DNS());
+ } else {
+ inst->pool->hostname = slapi_ch_strdup( ludp->lud_host );
+ }
+ inst->pool->url = slapi_ch_strdup( url);
+ inst->pool->secure = (( ludp->lud_options & LDAP_URL_OPT_SECURE ) != 0 );
+
+ if ((ludp->lud_port==0) && inst->pool->secure)
+ inst->pool->port=CB_LDAP_SECURE_PORT;
+ else
+ inst->pool->port = ludp->lud_port;
+
+ /* Build a charray of <host>:<port> */
+ /* hostname is of the form <host>[:port] <host>[:port] */
+
+ { char * aBufCopy, * aHostName;
+ char * iter = NULL;
+ aBufCopy= aBufCopy=slapi_ch_strdup(inst->pool->hostname);
+
+ aHostName=ldap_utf8strtok_r(aBufCopy," ", &iter);
+ charray_free(inst->url_array);
+ inst->url_array=NULL;
+ while (aHostName) {
+
+ char * aHostPort = slapi_ch_calloc(1,strlen(aHostName)+30);
+ if ( NULL == ( ptr=strstr(aHostName,":")))
+ sprintf(aHostPort,"%s://%s:%d/",
+ inst->pool->secure ? "ldaps" : "ldap",
+ aHostName,inst->pool->port);
+ else
+ sprintf(aHostPort,"%s://%s/",
+ inst->pool->secure ? "ldaps" : "ldap",
+ aHostName);
+
+ charray_add(&inst->url_array,aHostPort);
+ aHostName=ldap_utf8strtok_r(NULL," ", &iter);
+ }
+
+ slapi_ch_free((void **) &aBufCopy);
+ }
+
+ inst->bind_pool->port=inst->pool->port;
+ inst->bind_pool->secure=inst->pool->secure;
+ inst->bind_pool->hostname=slapi_ch_strdup(inst->pool->hostname);
+
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ }
+
+ if ( ludp != NULL ) {
+ ldap_free_urldesc( ludp );
+ }
+ return rc;
+}
+
+static void *cb_instance_binduser_get(void *arg)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+ char * data;
+
+ PR_RWLock_Rlock(inst->rwl_config_lock);
+ data = slapi_ch_strdup(inst->pool->binddn2); /* not normalized */
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ return data;
+}
+
+static int cb_instance_binduser_set(void *arg, void *value, char *errorbuf, int phase, int apply)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+ int rc=LDAP_SUCCESS;
+
+ if (apply) {
+
+ PR_RWLock_Wlock(inst->rwl_config_lock);
+ if (( phase != CB_CONFIG_PHASE_INITIALIZATION ) &&
+ ( phase != CB_CONFIG_PHASE_STARTUP )) {
+
+ /* Dynamic modif */
+ /* Free user later */
+
+ charray_add(&inst->pool->waste_basket,inst->pool->binddn);
+ charray_add(&inst->pool->waste_basket,inst->pool->binddn2);
+ rc=CB_REOPEN_CONN;
+ }
+
+ inst->pool->binddn=slapi_ch_strdup((char *) value);
+ inst->pool->binddn2=slapi_ch_strdup((char *) value);
+ slapi_dn_normalize_case(inst->pool->binddn);
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ } else {
+
+ /* Security check */
+ /* directory manager of the farm server should not be used as */
+ /* proxing user. This is hard to check, so assume same directory */
+ /* manager across servers. */
+
+ char * rootdn = cb_get_rootdn();
+ char * theValueCopy = NULL;
+
+ if (value) {
+ theValueCopy=slapi_ch_strdup((char *) value);
+ slapi_dn_normalize_case(theValueCopy);
+ }
+
+ PR_RWLock_Rlock(inst->rwl_config_lock);
+ if (inst->impersonate && theValueCopy &&
+ !strcmp(theValueCopy,rootdn)) { /* UTF8-aware. See cb_get_dn() */
+ rc=LDAP_UNWILLING_TO_PERFORM;
+ if (errorbuf) {
+ sprintf(errorbuf,"value %s not allowed",rootdn);
+ }
+ }
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+
+ slapi_ch_free((void **)&theValueCopy);
+ slapi_ch_free((void **)&rootdn);
+ }
+
+ return rc;
+}
+
+
+static void *cb_instance_userpassword_get(void *arg)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+ char * data;
+
+ PR_RWLock_Rlock(inst->rwl_config_lock);
+ data = slapi_ch_strdup(inst->pool->password);
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ return data;
+}
+
+static int cb_instance_userpassword_set(void *arg, void *value, char *errorbuf, int phase, int apply)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+ int rc=LDAP_SUCCESS;
+
+ if (apply) {
+ PR_RWLock_Wlock(inst->rwl_config_lock);
+ if (( phase != CB_CONFIG_PHASE_INITIALIZATION ) &&
+ ( phase != CB_CONFIG_PHASE_STARTUP )) {
+
+ /* Dynamic modif */
+ charray_add(&inst->pool->waste_basket,inst->pool->password);
+ rc=CB_REOPEN_CONN;
+ }
+
+ inst->pool->password=slapi_ch_strdup((char *) value);
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ }
+ return rc;
+}
+
+static void *cb_instance_sizelimit_get(void *arg)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+ int data;
+
+ PR_RWLock_Rlock(inst->rwl_config_lock);
+ data = inst->sizelimit;
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ return (void *) data;
+}
+
+static int cb_instance_sizelimit_set(void *arg, void *value, char *errorbuf, int phase, int apply)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+ if (apply) {
+ PR_RWLock_Wlock(inst->rwl_config_lock);
+ inst->sizelimit=(int) value;
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ if (inst->inst_be)
+ be_set_sizelimit(inst->inst_be, (int) value);
+ }
+ return LDAP_SUCCESS;
+}
+
+static void *cb_instance_timelimit_get(void *arg)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+ int data;
+
+ PR_RWLock_Rlock(inst->rwl_config_lock);
+ data = inst->timelimit;
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ return (void *) data;
+}
+
+static int cb_instance_timelimit_set(void *arg, void *value, char *errorbuf, int phase, int apply)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+ if (apply) {
+ PR_RWLock_Wlock(inst->rwl_config_lock);
+ inst->timelimit=(int) value;
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ if (inst->inst_be)
+ be_set_timelimit(inst->inst_be, (int) value);
+ }
+ return LDAP_SUCCESS;
+}
+
+static void *cb_instance_max_test_get(void *arg)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+ int data;
+
+ PR_RWLock_Rlock(inst->rwl_config_lock);
+ data = inst->max_test_time;
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ return (void *) data;
+}
+
+static int cb_instance_max_test_set(void *arg, void *value, char *errorbuf, int phase, int apply)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+ if (apply) {
+ PR_RWLock_Wlock(inst->rwl_config_lock);
+ inst->max_test_time=(int) value;
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ }
+ return LDAP_SUCCESS;
+}
+
+static void *cb_instance_max_idle_get(void *arg)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+ int data;
+
+ PR_RWLock_Rlock(inst->rwl_config_lock);
+ data = inst->max_idle_time;
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ return (void *) data;
+}
+
+static int cb_instance_max_idle_set(void *arg, void *value, char *errorbuf, int phase, int apply)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+ if (apply) {
+ PR_RWLock_Wlock(inst->rwl_config_lock);
+ inst->max_idle_time=(int) value;
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ }
+ return LDAP_SUCCESS;
+}
+
+
+static void *cb_instance_hoplimit_get(void *arg)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+ int data;
+
+ PR_RWLock_Rlock(inst->rwl_config_lock);
+ data = inst->hoplimit;
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ return (void *) data;
+}
+
+static int cb_instance_hoplimit_set(void *arg, void *value, char *errorbuf, int phase, int apply)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+ if (apply) {
+ PR_RWLock_Wlock(inst->rwl_config_lock);
+ inst->hoplimit=(int) value;
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ }
+ return LDAP_SUCCESS;
+}
+
+static void *cb_instance_maxbconn_get(void *arg)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+ int data;
+
+ PR_RWLock_Rlock(inst->rwl_config_lock);
+ data = inst->bind_pool->conn.maxconnections;
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ return (void *) data;
+}
+
+static int cb_instance_maxbconn_set(void *arg, void *value, char *errorbuf, int phase, int apply)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+ if (apply) {
+ PR_RWLock_Wlock(inst->rwl_config_lock);
+ inst->bind_pool->conn.maxconnections=(int) value;
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ }
+ return LDAP_SUCCESS;
+}
+
+static void *cb_instance_maxconn_get(void *arg)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+ int data;
+
+ PR_RWLock_Rlock(inst->rwl_config_lock);
+ data = inst->pool->conn.maxconnections;
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ return (void *) data;
+}
+
+static int cb_instance_maxconn_set(void *arg, void *value, char *errorbuf, int phase, int apply)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+ if (apply) {
+ PR_RWLock_Wlock(inst->rwl_config_lock);
+ inst->pool->conn.maxconnections=(int) value;
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ }
+ return LDAP_SUCCESS;
+}
+
+static void *cb_instance_abandonto_get(void *arg)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+ int data;
+
+ PR_RWLock_Rlock(inst->rwl_config_lock);
+ data = inst->abandon_timeout.tv_sec;
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ return (void *) data;
+}
+
+static int cb_instance_abandonto_set(void *arg, void *value, char *errorbuf, int phase, int apply)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+
+ if (apply) {
+ if (( phase != CB_CONFIG_PHASE_INITIALIZATION ) &&
+ ( phase != CB_CONFIG_PHASE_STARTUP )) {
+
+ /* Dynamic modif not supported */
+ /* Stored in ldif only */
+ return LDAP_SUCCESS;
+ }
+
+ PR_RWLock_Wlock(inst->rwl_config_lock);
+ inst->abandon_timeout.tv_sec=(int) value;
+ inst->abandon_timeout.tv_usec=0;
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ }
+ return LDAP_SUCCESS;
+}
+
+static void *cb_instance_maxbconc_get(void *arg)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+ int data;
+
+ PR_RWLock_Rlock(inst->rwl_config_lock);
+ data = inst->bind_pool->conn.maxconcurrency;
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ return (void *) data;
+}
+
+static int cb_instance_maxbconc_set(void *arg, void *value, char *errorbuf, int phase, int apply)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+ if (apply) {
+ PR_RWLock_Wlock(inst->rwl_config_lock);
+ inst->bind_pool->conn.maxconcurrency=(int) value;
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ }
+ return LDAP_SUCCESS;
+}
+
+static void *cb_instance_maxconc_get(void *arg)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+ int data;
+
+ PR_RWLock_Rlock(inst->rwl_config_lock);
+ data = inst->pool->conn.maxconcurrency;
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ return (void *) data;
+}
+
+static int cb_instance_maxconc_set(void *arg, void *value, char *errorbuf, int phase, int apply)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+ if (apply) {
+ PR_RWLock_Wlock(inst->rwl_config_lock);
+ inst->pool->conn.maxconcurrency=(int) value;
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ }
+ return LDAP_SUCCESS;
+}
+
+static void *cb_instance_imperson_get(void *arg)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+ int data;
+
+ PR_RWLock_Rlock(inst->rwl_config_lock);
+ data = inst->impersonate;
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ return (void *) data;
+}
+
+static int cb_instance_imperson_set(void *arg, void *value, char *errorbuf, int phase, int apply)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+ int rc=LDAP_SUCCESS;
+
+ if (apply) {
+ PR_RWLock_Wlock(inst->rwl_config_lock);
+ inst->impersonate=(int) value;
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ } else {
+ /* Security check: Make sure the proxing user is */
+ /* not the directory manager. */
+
+ char * rootdn=cb_get_rootdn();
+
+ PR_RWLock_Rlock(inst->rwl_config_lock);
+ if (((int) value) && inst->pool && inst->pool->binddn &&
+ !strcmp(inst->pool->binddn,rootdn)) { /* UTF-8 aware */
+ rc=LDAP_UNWILLING_TO_PERFORM;
+ if (errorbuf)
+ sprintf(errorbuf,"Proxy mode incompatible with %s value (%s not allowed)",
+ CB_CONFIG_BINDUSER,rootdn);
+ }
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ slapi_ch_free((void **)&rootdn);
+ }
+
+ return rc;
+}
+
+static void *cb_instance_connlife_get(void *arg)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+ int data;
+
+ PR_RWLock_Rlock(inst->rwl_config_lock);
+ data=inst->pool->conn.connlifetime;
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ return (void *) data;
+}
+
+static int cb_instance_connlife_set(void *arg, void *value, char *errorbuf, int phase, int apply)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+ if (apply) {
+ PR_RWLock_Wlock(inst->rwl_config_lock);
+ inst->pool->conn.connlifetime=(int) value;
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ }
+ return LDAP_SUCCESS;
+}
+
+static void *cb_instance_bindto_get(void *arg)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+ int data;
+
+ PR_RWLock_Rlock(inst->rwl_config_lock);
+ data=inst->bind_pool->conn.op_timeout.tv_sec;
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ return (void *) data;
+}
+
+static int cb_instance_bindto_set(void *arg, void *value, char *errorbuf, int phase, int apply)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+ if (apply) {
+ PR_RWLock_Wlock(inst->rwl_config_lock);
+ inst->bind_pool->conn.op_timeout.tv_sec=(int) value;
+ inst->bind_pool->conn.op_timeout.tv_usec=0;
+ inst->bind_pool->conn.bind_timeout.tv_sec=(int) value;
+ inst->bind_pool->conn.bind_timeout.tv_usec=0;
+ /* Used to bind to the farm server */
+ inst->pool->conn.bind_timeout.tv_sec=(int) value;
+ inst->pool->conn.bind_timeout.tv_usec=0;
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ }
+ return LDAP_SUCCESS;
+}
+
+static void *cb_instance_opto_get(void *arg)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+ int data;
+
+ PR_RWLock_Rlock(inst->rwl_config_lock);
+ data=inst->pool->conn.op_timeout.tv_sec;
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ return (void *) data;
+}
+
+static int cb_instance_opto_set(void *arg, void *value, char *errorbuf, int phase, int apply)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+ if (apply) {
+ PR_RWLock_Wlock(inst->rwl_config_lock);
+ inst->pool->conn.op_timeout.tv_sec=(int) value;
+ inst->pool->conn.op_timeout.tv_usec=0;
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ }
+ return LDAP_SUCCESS;
+}
+
+static void *cb_instance_ref_get(void *arg)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+ int data;
+
+ PR_RWLock_Rlock(inst->rwl_config_lock);
+ data=inst->searchreferral;
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ return (void *) data;
+}
+
+static int cb_instance_ref_set(void *arg, void *value, char *errorbuf, int phase, int apply)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+ if (apply) {
+ PR_RWLock_Wlock(inst->rwl_config_lock);
+ inst->searchreferral=(int) value;
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ }
+ return LDAP_SUCCESS;
+}
+
+static void *cb_instance_acl_get(void *arg)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+ int data;
+
+ PR_RWLock_Rlock(inst->rwl_config_lock);
+ data=inst->local_acl;
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ return (void *) data;
+}
+
+static int cb_instance_acl_set(void *arg, void *value, char *errorbuf, int phase, int apply)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+
+ if (apply) {
+ if (( phase != CB_CONFIG_PHASE_INITIALIZATION ) &&
+ ( phase != CB_CONFIG_PHASE_STARTUP )) {
+
+ /* Dynamic modif not supported */
+ /* Stored in ldif only */
+ return LDAP_SUCCESS;
+ }
+ PR_RWLock_Wlock(inst->rwl_config_lock);
+ inst->local_acl=(int) value;
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ }
+ return LDAP_SUCCESS;
+}
+
+static void *cb_instance_bindretry_get(void *arg)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+ int data;
+
+ PR_RWLock_Rlock(inst->rwl_config_lock);
+ data=inst->bind_retry;
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ return (void *) data;
+}
+
+static int cb_instance_bindretry_set(void *arg, void *value, char *errorbuf, int phase, int apply)
+{
+ cb_backend_instance * inst=(cb_backend_instance *) arg;
+ if (apply) {
+ PR_RWLock_Wlock(inst->rwl_config_lock);
+ inst->bind_retry=(int) value;
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ }
+ return LDAP_SUCCESS;
+}
+
+
+
+
+/* Finds an entry in a config_info array with the given name. Returns
+ * the entry on success and NULL when not found.
+ */
+static cb_instance_config_info *cb_get_config_info(cb_instance_config_info *config_array, char *attr_name)
+{
+ int x;
+
+ for(x = 0; config_array[x].config_name != NULL; x++) {
+ if (!strcasecmp(config_array[x].config_name, attr_name)) {
+ return &(config_array[x]);
+ }
+ }
+ return NULL;
+}
+
+/*
+** Update an attribute value
+** For now, unknown attributes are ignored
+** Return a LDAP error code OR CB_REOPEN_CONN when the
+** update requires to close open connections.
+*/
+
+static int
+cb_instance_config_set(void *arg, char *attr_name, cb_instance_config_info *config_array,
+struct berval *bval, char *err_buf, int phase, int apply_mod)
+{
+ cb_instance_config_info *config;
+ int use_default;
+ int int_val;
+ long long_val;
+ int retval=LDAP_LOCAL_ERROR;
+
+ config = cb_get_config_info(config_array, attr_name);
+ if (NULL == config) {
+ /* Ignore unknown attributes */
+ return LDAP_SUCCESS;
+ }
+
+ /* If the config phase is initialization or if bval is NULL, we will use
+ * the default value for the attribute. */
+ if (CB_CONFIG_PHASE_INITIALIZATION == phase || NULL == bval) {
+ use_default = 1;
+ } else {
+ use_default = 0;
+ /* Since we are setting the value for the config attribute, we
+ * need to turn on the CB_PREVIOUSLY_SET flag to make
+ * sure this attribute is shown. */
+ config->config_flags |= CB_PREVIOUSLY_SET;
+ }
+
+ switch(config->config_type) {
+ case CB_CONFIG_TYPE_INT:
+ if (use_default) {
+ int_val = cb_atoi(config->config_default_value);
+ } else {
+ int_val = cb_atoi((char *)bval->bv_val);
+ }
+ retval = config->config_set_fn(arg, (void *) int_val, err_buf, phase, apply_mod);
+ break;
+ case CB_CONFIG_TYPE_INT_OCTAL:
+ if (use_default) {
+ int_val = (int) strtol(config->config_default_value, NULL, 8);
+ } else {
+ int_val = (int) strtol((char *)bval->bv_val, NULL, 8);
+ }
+ retval = config->config_set_fn(arg, (void *) int_val, err_buf, phase, apply_mod);
+ break;
+ case CB_CONFIG_TYPE_LONG:
+ if (use_default) {
+ long_val = cb_atol(config->config_default_value);
+ } else {
+ long_val = cb_atol((char *)bval->bv_val);
+ }
+ retval = config->config_set_fn(arg, (void *) long_val, err_buf, phase, apply_mod);
+ break;
+ case CB_CONFIG_TYPE_STRING:
+ if (use_default) {
+ retval = config->config_set_fn(arg, config->config_default_value, err_buf, phase, apply_mod);
+ } else {
+ retval = config->config_set_fn(arg, bval->bv_val, err_buf, phase, apply_mod);
+ }
+ break;
+ case CB_CONFIG_TYPE_ONOFF:
+ if (use_default) {
+ int_val = !strcasecmp(config->config_default_value, "on");
+ } else {
+ int_val = !strcasecmp((char *) bval->bv_val, "on");
+ }
+ retval = config->config_set_fn(arg, (void *) int_val, err_buf, phase, apply_mod);
+ break;
+ }
+ return retval;
+}
+
+/* Utility function used in creating config entries. Using the
+ * config_info, this function gets info and formats in the correct
+ * way.
+ */
+void cb_instance_config_get(void *arg, cb_instance_config_info *config, char *buf)
+{
+ char *tmp_string;
+
+ if (config == NULL) {
+ buf[0] = '\0';
+ }
+
+ switch(config->config_type) {
+ case CB_CONFIG_TYPE_INT:
+ sprintf(buf, "%d", (int) config->config_get_fn(arg));
+ break;
+ case CB_CONFIG_TYPE_INT_OCTAL:
+ sprintf(buf, "%o", (int) config->config_get_fn(arg));
+ break;
+ case CB_CONFIG_TYPE_LONG:
+ sprintf(buf, "%d", (long) config->config_get_fn(arg));
+ break;
+ case CB_CONFIG_TYPE_STRING:
+ /* Remember the get function for strings returns memory
+ * that must be freed. */
+ tmp_string = (char *) config->config_get_fn(arg);
+ sprintf(buf, "%s", (char *) tmp_string);
+ slapi_ch_free((void **)&tmp_string);
+ break;
+ case CB_CONFIG_TYPE_ONOFF:
+ if ((int) config->config_get_fn(arg)) {
+ sprintf(buf,"%s","on");
+ } else {
+ sprintf(buf,"%s","off");
+ }
+ break;
+ default:
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "Invalid attribute syntax.\n");
+
+ }
+}
+
+/*
+** Search for instance config entry
+** Always return 'active' values because some configuration changes
+** won't be taken into account until the server restarts
+*/
+
+int cb_instance_search_config_callback(Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter,
+ int *returncode, char *returntext, void *arg) {
+
+ char buf[CB_BUFSIZE];
+ struct berval val;
+ struct berval *vals[2];
+ int i = 0;
+ cb_backend_instance *inst = (cb_backend_instance *)arg;
+ cb_instance_config_info * config;
+
+ vals[0] = &val;
+ vals[1] = NULL;
+
+ /* suffixes */
+
+ PR_RWLock_Rlock(inst->rwl_config_lock);
+
+ {
+ const Slapi_DN *aSuffix;
+ i=0;
+ if (inst->inst_be) {
+ while ((aSuffix=slapi_be_getsuffix(inst->inst_be,i))) {
+ val.bv_val = (char *)slapi_sdn_get_dn(aSuffix);
+ val.bv_len = strlen( val.bv_val );
+ if (val.bv_len) {
+ if (i==0)
+ slapi_entry_attr_replace(e,CB_CONFIG_SUFFIX,(struct berval **)vals );
+ else
+ slapi_entry_attr_merge(e,CB_CONFIG_SUFFIX,(struct berval **)vals );
+ }
+ i++;
+ }
+ }
+ }
+
+ for (i=0; inst->chaining_components && inst->chaining_components[i]; i++) {
+ val.bv_val = inst->chaining_components[i];
+ val.bv_len = strlen( val.bv_val );
+ if (val.bv_len) {
+ if (i==0)
+ slapi_entry_attr_replace(e,CB_CONFIG_CHAINING_COMPONENTS,(struct berval **)vals );
+ else
+ slapi_entry_attr_merge(e,CB_CONFIG_CHAINING_COMPONENTS,(struct berval **)vals );
+ }
+ }
+
+ for (i=0; inst->illegal_attributes && inst->illegal_attributes[i]; i++) {
+ val.bv_val = inst->illegal_attributes[i];
+ val.bv_len = strlen( val.bv_val );
+ if (val.bv_len) {
+ if (i==0)
+ slapi_entry_attr_replace(e,CB_CONFIG_ILLEGAL_ATTRS,(struct berval **)vals );
+ else
+ slapi_entry_attr_merge(e,CB_CONFIG_ILLEGAL_ATTRS,(struct berval **)vals );
+ }
+ }
+
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+
+ /* standard attributes */
+ for(config = cb_the_instance_config; config->config_name != NULL; config++) {
+ if (!(config->config_flags & (CB_ALWAYS_SHOW | CB_PREVIOUSLY_SET))) {
+ /* This config option shouldn't be shown */
+ continue;
+ }
+
+ cb_instance_config_get((void *) inst, config, buf);
+
+ val.bv_val = buf;
+ val.bv_len = strlen(buf);
+ if (val.bv_len)
+ slapi_entry_attr_replace(e, config->config_name, vals);
+ }
+
+ *returncode = LDAP_SUCCESS;
+ return SLAPI_DSE_CALLBACK_OK;
+}
+
+/*
+** Ooops!!! The backend instance is beeing deleted
+*/
+
+int cb_instance_delete_config_callback(Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* e2,
+ int *returncode, char *returntext, void *arg) {
+
+ cb_backend_instance * inst = (cb_backend_instance *) arg;
+ int rc;
+ Slapi_Entry * anEntry=NULL;
+ Slapi_DN * aDn;
+
+ CB_ASSERT( inst!=NULL );
+
+ /* notify the front-end */
+ slapi_mtn_be_stopping(inst->inst_be);
+
+ /* Now it is safe to stop */
+ /* No pending op */
+
+
+ /* unregister callbacks */
+ slapi_config_remove_callback(SLAPI_OPERATION_SEARCH, DSE_FLAG_PREOP, inst->configDn,
+ LDAP_SCOPE_BASE, "(objectclass=*)", cb_instance_search_config_callback);
+
+ slapi_config_remove_callback(SLAPI_OPERATION_DELETE, DSE_FLAG_POSTOP, inst->configDn,
+ LDAP_SCOPE_BASE, "(objectclass=*)", cb_instance_delete_config_callback);
+
+ slapi_config_remove_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_PREOP, inst->configDn,
+ LDAP_SCOPE_BASE, "(objectclass=*)", cb_instance_modify_config_check_callback);
+ slapi_config_remove_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_POSTOP, inst->configDn,
+ LDAP_SCOPE_BASE, "(objectclass=*)", cb_instance_modify_config_callback);
+
+ /* At this point, the monitor entry should have been removed */
+ /* If not, manually call delete callback */
+
+ aDn = slapi_sdn_new_dn_byref(inst->monitorDn);
+ if ( LDAP_SUCCESS==(slapi_search_internal_get_entry(aDn,NULL, &anEntry,inst->backend_type->identity))) {
+ cb_delete_monitor_callback( NULL, anEntry, NULL, &rc , NULL, inst );
+ if (anEntry)
+ slapi_entry_free(anEntry);
+ }
+ slapi_sdn_done(aDn);
+ slapi_sdn_free(&aDn);
+
+ /* free resources */
+ cb_close_conn_pool(inst->bind_pool);
+ cb_close_conn_pool(inst->pool);
+ slapi_be_free(&(inst->inst_be));
+ cb_instance_free(inst);
+
+ return SLAPI_DSE_CALLBACK_OK;
+}
+
+static void cb_instance_add_monitor_later(time_t when, void *arg) {
+
+ cb_backend_instance * inst = (cb_backend_instance *) arg;
+
+ if ( inst != NULL )
+ {
+ PR_RWLock_Rlock(inst->rwl_config_lock);
+
+ /* create the monitor entry if it is not there yet */
+ if (LDAP_SUCCESS == cb_config_add_dse_entries(inst->backend_type, cb_skeleton_entries,
+ inst->inst_name,CB_PLUGIN_NAME, NULL))
+ {
+
+ /* add monitor callbacks */
+ slapi_config_register_callback(SLAPI_OPERATION_SEARCH, DSE_FLAG_PREOP, inst->monitorDn, LDAP_SCOPE_BASE,
+ "(objectclass=*)", cb_search_monitor_callback, (void *) inst);
+
+ slapi_config_register_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_PREOP, inst->monitorDn, LDAP_SCOPE_BASE,
+ "(objectclass=*)", cb_dont_allow_that, (void *) NULL);
+
+ slapi_config_register_callback(SLAPI_OPERATION_DELETE, DSE_FLAG_PREOP , inst->monitorDn, LDAP_SCOPE_BASE,
+ "(objectclass=*)", cb_delete_monitor_callback, (void *) inst);
+ }
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ }
+}
+
+
+int cb_instance_add_config_check_callback(Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* e2,
+ int *returncode, char *returntext, void *arg) {
+
+ int rc=LDAP_SUCCESS;
+ cb_backend_instance *inst;
+ cb_backend *cb=(cb_backend *) arg;
+ Slapi_Attr *attr = NULL;
+ Slapi_Value *sval;
+ const struct berval *attrValue;
+ char *instname=NULL;
+
+ if (returntext)
+ returntext[0]='\0';
+
+ /* Basic entry check */
+ if ( 0 == slapi_entry_attr_find( e, CB_CONFIG_INSTNAME, &attr )) {
+ slapi_attr_first_value(attr, &sval);
+ attrValue = slapi_value_get_berval(sval);
+ instname=attrValue->bv_val;
+ }
+ if ( instname == NULL ) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "Malformed backend instance (<%s> missing)>\n", CB_CONFIG_INSTNAME);
+ *returncode = LDAP_LOCAL_ERROR;
+ return SLAPI_DSE_CALLBACK_ERROR;
+ }
+
+ /* Allocate a new backend internal data structure */
+ inst = cb_instance_alloc(cb,instname,slapi_entry_get_dn(e));
+
+ /* build the backend instance from the default hardcoded conf, */
+ /* the default instance config and the specific entry specified */
+ if ((rc=cb_build_backend_instance_config(inst,e,0))
+ != LDAP_SUCCESS) {
+ slapi_log_error( SLAPI_LOG_FATAL, CB_PLUGIN_SUBSYSTEM,
+ "Can't instantiate chaining backend instance %s.\n",inst->inst_name);
+ *returncode=rc;
+ cb_instance_free(inst);
+ return SLAPI_DSE_CALLBACK_ERROR;
+ }
+
+ /* Free the dummy instance */
+ *returncode=rc;
+ cb_instance_free(inst);
+
+ return SLAPI_DSE_CALLBACK_OK;
+}
+
+
+/* Create the default instance config from hard-coded values */
+/*
+** Initialize the backend instance with the config entry
+** passed in arguments.
+** <arg> : (cb_backend *)
+*/
+
+int cb_instance_add_config_callback(Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* e2,
+ int *returncode, char *returntext, void *arg) {
+
+ int rc=LDAP_SUCCESS;
+ cb_backend_instance *inst;
+ cb_backend *cb=(cb_backend *) arg;
+ Slapi_Attr *attr = NULL;
+ Slapi_Value *sval;
+ const struct berval *attrValue;
+ char *instname=NULL;
+
+ if (returntext)
+ returntext[0]='\0';
+
+ /* Basic entry check */
+ if ( 0 == slapi_entry_attr_find( e, CB_CONFIG_INSTNAME, &attr )) {
+ slapi_attr_first_value(attr, &sval);
+ attrValue = slapi_value_get_berval(sval);
+ instname=attrValue->bv_val;
+ }
+ if ( instname == NULL ) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "Malformed backend instance (<%s> missing)>\n", CB_CONFIG_INSTNAME);
+ *returncode = LDAP_LOCAL_ERROR;
+ return SLAPI_DSE_CALLBACK_ERROR;
+ }
+
+ /* Allocate a new backend internal data structure */
+ inst = cb_instance_alloc(cb,instname,slapi_entry_get_dn(e));
+
+ /* build the backend instance from the default hardcoded conf, */
+ /* the default instance config and the specific entry specified */
+ if ((rc=cb_build_backend_instance_config(inst,e,0))
+ != LDAP_SUCCESS) {
+ slapi_log_error( SLAPI_LOG_FATAL, CB_PLUGIN_SUBSYSTEM,
+ "Can't instantiate chaining backend instance %s.\n",inst->inst_name);
+ *returncode=rc;
+ cb_instance_free(inst);
+ return SLAPI_DSE_CALLBACK_ERROR;
+ }
+
+ /* Instantiate a Slapi_Backend if necessary */
+ if (!inst->isconfigured) {
+
+ Slapi_PBlock *aPb=NULL;
+
+ inst->inst_be = slapi_be_new(CB_CHAINING_BACKEND_TYPE,slapi_ch_strdup(inst->inst_name),0,0);
+ aPb=slapi_pblock_new();
+ slapi_pblock_set(aPb, SLAPI_PLUGIN, inst->backend_type->plugin);
+ slapi_be_setentrypoint(inst->inst_be,0,(void *)NULL,aPb);
+ slapi_be_set_instance_info(inst->inst_be,inst);
+ slapi_pblock_set(aPb, SLAPI_PLUGIN, NULL);
+ slapi_pblock_destroy(aPb);
+ }
+
+ cb_build_backend_instance_config(inst,e,1);
+
+ /* kexcoff: the order of the following calls is very important to prevent the deletion of the
+ instance to happen before the creation of the monitor part of the config.
+ However, not sure it solves all the situations, but at least it is worth to maintain
+ this order. */
+
+ if (!inst->isconfigured)
+ {
+ /* Add monitor entry and callback on it
+ * called from an add...
+ * we can't call recursively into the DSE to do more adds, they'll
+ * silently fail. instead, schedule the adds to happen in 1 second.
+ */
+ inst->eq_ctx = slapi_eq_once(cb_instance_add_monitor_later, (void *)inst, time(NULL)+1);
+ }
+
+ /* Get the list of operational attrs defined in the schema */
+ /* see cb_search file for a reason for that */
+
+ inst->every_attribute=slapi_schema_list_attribute_names(SLAPI_ATTR_FLAG_OPATTR);
+ charray_add(&inst->every_attribute,slapi_ch_strdup(LDAP_ALL_USER_ATTRS));
+
+ if (!inst->isconfigured)
+ {
+ slapi_config_register_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_PREOP, inst->configDn,
+ LDAP_SCOPE_BASE,"(objectclass=*)",cb_instance_modify_config_check_callback, (void *) inst);
+ slapi_config_register_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_POSTOP, inst->configDn,
+ LDAP_SCOPE_BASE,"(objectclass=*)",cb_instance_modify_config_callback, (void *) inst);
+
+ slapi_config_register_callback(SLAPI_OPERATION_SEARCH, DSE_FLAG_PREOP, inst->configDn,
+ LDAP_SCOPE_BASE,"(objectclass=*)", cb_instance_search_config_callback, (void *) inst);
+
+ /* allow deletion otherwise impossible to remote a backend instance */
+ /* dynamically... */
+ slapi_config_register_callback(SLAPI_OPERATION_DELETE, DSE_FLAG_POSTOP, inst->configDn,
+ LDAP_SCOPE_BASE,"(objectclass=*)", cb_instance_delete_config_callback, (void *) inst);
+ }
+
+ /* Notify the front-end */
+ /* After the call below, we can receive ops */
+ slapi_mtn_be_started(inst->inst_be);
+
+ inst->isconfigured=1;
+ return SLAPI_DSE_CALLBACK_OK;
+}
+
+
+/* Create the default instance config from hard-coded values */
+
+int cb_create_default_backend_instance_config(cb_backend * cb) {
+
+
+ int rc;
+ cb_backend_instance *dummy;
+ Slapi_Entry *e=slapi_entry_alloc();
+ char defaultDn[CB_BUFSIZE];
+ char *olddn;
+ struct berval val;
+ struct berval *vals[2];
+ Slapi_PBlock *pb;
+
+ dummy = cb_instance_alloc(cb, "dummy", "o=dummy");
+ cb_instance_config_set_default(dummy);
+ cb_instance_search_config_callback(NULL,e,NULL, &rc, NULL,(void *) dummy);
+
+
+ /* set right dn and objectclass */
+
+ sprintf(defaultDn,"cn=default instance config,%s",cb->pluginDN);
+ olddn = slapi_entry_get_dn(e);
+ slapi_ch_free((void **) &olddn);
+
+ slapi_entry_set_dn(e,slapi_ch_strdup(defaultDn));
+
+ vals[0] = &val;
+ vals[1] = NULL;
+
+ val.bv_val = "top";
+ val.bv_len = strlen( val.bv_val );
+ slapi_entry_attr_replace( e, "objectclass", (struct berval **)vals );
+ val.bv_val = CB_CONFIG_EXTENSIBLEOCL;
+ val.bv_len = strlen( val.bv_val );
+ slapi_entry_attr_merge( e, "objectclass", (struct berval **)vals );
+ val.bv_val = "default instance config";
+ val.bv_len = strlen( val.bv_val );
+ slapi_entry_attr_replace( e, "cn", (struct berval **)vals );
+
+ /* create entry */
+ pb = slapi_pblock_new();
+ slapi_add_entry_internal_set_pb(pb, e, NULL, cb->identity, 0);
+ slapi_add_internal_pb(pb);
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
+ if ( LDAP_SUCCESS != rc ) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "Add %s failed (%s)\n",defaultDn,ldap_err2string(rc));
+ }
+
+ slapi_pblock_destroy(pb);
+ /* cleanup */
+ cb_instance_free(dummy);
+ /* BEWARE: entry is consummed */
+ return rc;
+}
+
+/* Extract backend instance configuration from the LDAP entry */
+
+int cb_build_backend_instance_config(cb_backend_instance *inst, Slapi_Entry * conf, int apply) {
+
+ cb_backend *cb = inst->backend_type;
+ Slapi_PBlock *default_pb;
+ Slapi_Entry **default_entries = NULL;
+ Slapi_Entry *default_conf=NULL;
+ int default_res, rc;
+ char defaultDn[CB_BUFSIZE];
+ cb_backend_instance * current_inst;
+
+ rc=LDAP_SUCCESS;
+
+ if (apply)
+ current_inst=inst;
+ else
+ current_inst=cb_instance_alloc(cb,inst->inst_name,"cn=dummy");
+
+ /* set default configuration */
+ cb_instance_config_set_default(current_inst);
+
+ /* 2: Overwrite values present in the default instance config */
+
+ sprintf(defaultDn,"cn=default instance config,%s",cb->pluginDN);
+
+ default_pb = slapi_pblock_new();
+ slapi_search_internal_set_pb(default_pb, defaultDn, LDAP_SCOPE_BASE,
+ "objectclass=*", NULL, 0, NULL, NULL, cb->identity, 0);
+ slapi_search_internal_pb (default_pb);
+ slapi_pblock_get(default_pb, SLAPI_PLUGIN_INTOP_RESULT, &default_res);
+ if ( LDAP_SUCCESS == default_res ) {
+ slapi_pblock_get(default_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &default_entries);
+ if (default_entries && default_entries[0] ) {
+
+ struct berval val;
+ struct berval *vals[2];
+ vals[0] = &val;
+ vals[1] = NULL;
+ default_conf=default_entries[0];
+
+ /* hack: add a dummy url (mandatory) to avoid error */
+ /* will be overwritten by the one in conf entry */
+ val.bv_val = "ldap://localhost/";
+ val.bv_len = strlen( val.bv_val );
+ slapi_entry_attr_replace( default_conf, CB_CONFIG_HOSTURL, (struct berval **)vals );
+
+ rc=cb_instance_config_initialize(current_inst,default_conf,CB_CONFIG_PHASE_STARTUP,1);
+ }
+ }
+ slapi_free_search_results_internal(default_pb);
+ slapi_pblock_destroy(default_pb);
+
+ if (rc == LDAP_SUCCESS)
+ rc=cb_instance_config_initialize(current_inst,conf,CB_CONFIG_PHASE_STARTUP,1);
+
+ if (!apply)
+ cb_instance_free(current_inst);
+
+ return rc;
+}
diff --git a/ldap/servers/plugins/chainingdb/cb_modify.c b/ldap/servers/plugins/chainingdb/cb_modify.c
new file mode 100644
index 00000000..51aed6a3
--- /dev/null
+++ b/ldap/servers/plugins/chainingdb/cb_modify.c
@@ -0,0 +1,244 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include "cb.h"
+
+static void cb_remove_illegal_mods(cb_backend_instance * inst, LDAPMod **mods);
+
+/*
+ * Perform a modify operation
+ *
+ * Returns:
+ * 0 - success
+ * <0 - fail
+ *
+ */
+
+int
+chaining_back_modify ( Slapi_PBlock *pb )
+{
+
+ Slapi_Backend *be;
+ cb_backend_instance *cb;
+ LDAPControl **ctrls, **serverctrls;
+ int rc,parse_rc,msgid,i;
+ LDAP *ld=NULL;
+ char **referrals=NULL;
+ LDAPMod ** mods;
+ LDAPMessage * res;
+ char *dn,* matched_msg, *error_msg;
+ char *cnxerrbuf=NULL;
+ time_t endtime;
+ cb_outgoing_conn *cnx;
+
+ if ( LDAP_SUCCESS != (rc=cb_forward_operation(pb) )) {
+ cb_send_ldap_result( pb, rc, NULL, "Chaining forbidden", 0, NULL );
+ return -1;
+ }
+
+ slapi_pblock_get( pb, SLAPI_BACKEND, &be );
+ cb = cb_get_instance(be);
+
+ cb_update_monitor_info(pb,cb,SLAPI_OPERATION_MODIFY);
+
+ /* Check wether the chaining BE is available or not */
+ if ( cb_check_availability( cb, pb ) == FARMSERVER_UNAVAILABLE ){
+ return -1;
+ }
+
+ slapi_pblock_get( pb, SLAPI_MODIFY_TARGET, &dn );
+
+ if (cb_debug_on()) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,"modify: target:<%s>\n",dn);
+ }
+
+
+ ctrls=serverctrls=NULL;
+ slapi_pblock_get( pb, SLAPI_MODIFY_MODS, &mods );
+ slapi_pblock_get( pb, SLAPI_REQCONTROLS, &ctrls );
+
+ /* Check acls */
+
+ if ( cb->local_acl && !cb->associated_be_is_disabled ) {
+ char * errbuf=NULL;
+ Slapi_Entry *te = slapi_entry_alloc();
+ slapi_entry_set_dn(te,slapi_ch_strdup(dn));
+ rc = slapi_acl_check_mods( pb, te, mods, &errbuf);
+ slapi_entry_free(te);
+
+ if ( rc != LDAP_SUCCESS ) {
+ cb_send_ldap_result( pb, rc, NULL, errbuf, 0, NULL );
+ slapi_ch_free((void **)&errbuf);
+ return -1;
+ }
+ }
+
+
+ /* Grab a connection handle */
+ if ((rc = cb_get_connection(cb->pool,&ld,&cnx,NULL,&cnxerrbuf)) != LDAP_SUCCESS) {
+ cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL, cnxerrbuf, 0, NULL);
+ slapi_ch_free((void **)&cnxerrbuf);
+ /* ping the farm. If the farm is unreachable, we increment the counter */
+ cb_ping_farm(cb,NULL,0);
+ return -1;
+ }
+
+ /* Control management */
+ if ( (rc = cb_update_controls( pb,ld,&ctrls,CB_UPDATE_CONTROLS_ADDAUTH )) != LDAP_SUCCESS ) {
+ cb_send_ldap_result( pb, rc, NULL,NULL, 0, NULL);
+ cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc));
+ /* Don't free mods here: are freed at the do_modify level */
+ return -1;
+ }
+
+ if ( slapi_op_abandoned( pb )) {
+ cb_release_op_connection(cb->pool,ld,0);
+ /* Don't free mods here: are freed at the do_modify level */
+ if ( NULL != ctrls)
+ ldap_controls_free(ctrls);
+ return -1;
+ }
+
+ /* Remove illegal attributes from the mods */
+ cb_remove_illegal_mods(cb,mods);
+
+ /* heart-beat management */
+ if (cb->max_idle_time>0)
+ endtime=current_time() + cb->max_idle_time;
+
+ /* Send LDAP operation to the remote host */
+ rc = ldap_modify_ext( ld, dn, mods, ctrls, NULL, &msgid );
+ if ( NULL != ctrls)
+ ldap_controls_free(ctrls);
+
+ if ( rc != LDAP_SUCCESS ) {
+ cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL, ldap_err2string(rc), 0, NULL);
+ cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc));
+ return -1;
+ }
+
+ while ( 1 ) {
+
+ if (cb_check_forward_abandon(cb,pb,ld,msgid)) {
+ /* connection handle released */
+ return -1;
+ }
+
+ rc = ldap_result( ld, msgid, 0, &cb->abandon_timeout, &res );
+ switch ( rc ) {
+ case -1:
+ cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL,
+ ldap_err2string(rc), 0, NULL);
+ cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc));
+ if (res)
+ ldap_msgfree(res);
+ return -1;
+ case 0:
+ if ((rc=cb_ping_farm(cb,cnx,endtime)) != LDAP_SUCCESS) {
+
+ /* does not respond. give up and return a*/
+ /* error to the client. */
+
+ /*cb_send_ldap_result(pb,LDAP_OPERATIONS_ERROR, NULL,
+ ldap_err2string(rc), 0, NULL);*/
+ cb_send_ldap_result(pb,LDAP_OPERATIONS_ERROR, NULL, "FARM SERVER TEMPORARY UNAVAILABLE", 0, NULL);
+ cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc));
+ if (res)
+ ldap_msgfree(res);
+ return -1;
+ }
+#ifdef CB_YIELD
+ DS_Sleep(PR_INTERVAL_NO_WAIT);
+#endif
+ break;
+ default:
+
+ matched_msg=error_msg=NULL;
+ serverctrls=NULL;
+ parse_rc = ldap_parse_result( ld, res, &rc, &matched_msg,
+ &error_msg, &referrals, &serverctrls, 1 );
+ if ( parse_rc != LDAP_SUCCESS ) {
+ cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL,
+ ldap_err2string(parse_rc), 0, NULL);
+ cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(parse_rc));
+ slapi_ch_free((void **)&matched_msg);
+ slapi_ch_free((void **)&error_msg);
+ if (serverctrls)
+ ldap_controls_free(serverctrls);
+ /* jarnou: free referrals */
+ if (referrals)
+ charray_free(referrals);
+ return -1;
+ }
+
+ if ( rc != LDAP_SUCCESS ) {
+ struct berval ** refs = referrals2berval(referrals);
+ cb_send_ldap_result( pb, rc, matched_msg, error_msg, 0, refs);
+ cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc));
+ slapi_ch_free((void **)&matched_msg);
+ slapi_ch_free((void **)&error_msg);
+ if (refs)
+ ber_bvecfree(refs);
+ if (referrals)
+ charray_free(referrals);
+ if (serverctrls)
+ ldap_controls_free(serverctrls);
+ return -1;
+ }
+
+ cb_release_op_connection(cb->pool,ld,0);
+
+ /* Add control response sent by the farm server */
+
+ for (i=0; serverctrls && serverctrls[i];i++)
+ slapi_pblock_set( pb, SLAPI_ADD_RESCONTROL, serverctrls[i]);
+ /* SLAPI_ADD_RESCONTROL dups controls */
+ if (serverctrls)
+ ldap_controls_free(serverctrls);
+ /* jarnou: free matched_msg, error_msg, and referrals if necessary */
+ slapi_ch_free((void **)&matched_msg);
+ slapi_ch_free((void **)&error_msg);
+ if (referrals)
+ charray_free(referrals);
+ cb_send_ldap_result( pb, LDAP_SUCCESS, NULL, NULL, 0, NULL );
+ return 0;
+ }
+ }
+
+ /* Never reached */
+ /* return 0; */
+}
+
+/* Function removes mods which are not allowed over-the-wire */
+static void
+cb_remove_illegal_mods(cb_backend_instance *inst, LDAPMod **mods)
+{
+ int i, j;
+ LDAPMod *tmp;
+
+ if ( inst->illegal_attributes != NULL ) { /* Unlikely to happen */
+
+ PR_RWLock_Wlock(inst->rwl_config_lock);
+
+ for (j=0; inst->illegal_attributes[j]; j++) {
+ for ( i = 0; mods[i] != NULL; i++ ) {
+ if (slapi_attr_types_equivalent(inst->illegal_attributes[j],mods[i]->mod_type)) {
+ tmp = mods[i];
+ for ( j = i; mods[j] != NULL; j++ ) {
+ mods[j] = mods[j + 1];
+ }
+ slapi_ch_free( (void**)&(tmp->mod_type) );
+ if ( tmp->mod_bvalues != NULL ) {
+ ber_bvecfree( tmp->mod_bvalues );
+ }
+ slapi_ch_free( (void**)&tmp );
+ i--;
+ }
+ }
+ }
+
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ }
+}
diff --git a/ldap/servers/plugins/chainingdb/cb_modrdn.c b/ldap/servers/plugins/chainingdb/cb_modrdn.c
new file mode 100644
index 00000000..e6b5dadb
--- /dev/null
+++ b/ldap/servers/plugins/chainingdb/cb_modrdn.c
@@ -0,0 +1,239 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include "cb.h"
+
+/*
+ * Perform a modrdn operation
+ *
+ * Returns:
+ * 0 - success
+ * <0 - fail
+ *
+ */
+
+int
+chaining_back_modrdn ( Slapi_PBlock *pb )
+{
+
+ Slapi_Backend * be;
+ cb_backend_instance *cb;
+ LDAPControl **ctrls, **serverctrls;
+ int rc,parse_rc,msgid,i;
+ LDAP *ld=NULL;
+ char **referrals=NULL;
+ LDAPMessage * res;
+ char * matched_msg, *error_msg,* pdn, *newdn, *dn;
+ int deleteoldrdn=0;
+ char * newsuperior, *newrdn;
+ char * cnxerrbuf=NULL;
+ time_t endtime;
+ cb_outgoing_conn * cnx;
+
+ if ( LDAP_SUCCESS != (rc=cb_forward_operation(pb) )) {
+ cb_send_ldap_result( pb, rc, NULL, "Chaining forbidden", 0, NULL );
+ return -1;
+ }
+
+ slapi_pblock_get( pb, SLAPI_BACKEND, &be );
+ cb = cb_get_instance(be);
+
+ cb_update_monitor_info(pb,cb,SLAPI_OPERATION_MODRDN);
+
+ /* Check wether the chaining BE is available or not */
+ if ( cb_check_availability( cb, pb ) == FARMSERVER_UNAVAILABLE ){
+ return -1;
+ }
+
+ newsuperior=newdn=newrdn=dn=NULL;
+ slapi_pblock_get( pb, SLAPI_MODRDN_TARGET, &dn );
+ slapi_pblock_get( pb, SLAPI_MODRDN_NEWRDN, &newrdn );
+ slapi_pblock_get( pb, SLAPI_MODRDN_NEWSUPERIOR, &newsuperior );
+ slapi_pblock_get( pb, SLAPI_MODRDN_DELOLDRDN, &deleteoldrdn );
+
+ /*
+ * Construct the new dn
+ */
+
+ dn = slapi_dn_normalize_case(dn);
+ if ( (pdn = slapi_dn_parent( dn )) != NULL ) {
+ /* parent + rdn + separator(s) + null */
+ newdn = (char *) slapi_ch_malloc( strlen( pdn ) + strlen( newrdn ) + 3 );
+ strcpy( newdn, newrdn );
+ strcat( newdn, "," );
+ strcat( newdn, pdn );
+
+ slapi_ch_free((void **)&pdn );
+
+ } else {
+ newdn = slapi_ch_strdup( newrdn );
+ }
+
+ /*
+ * Make sure the current backend is managing
+ * the new dn. We won't support moving entries
+ * across backend this way.
+ * Done in the front-end.
+ */
+
+ slapi_ch_free((void **)&newdn);
+
+ if (cb->local_acl && !cb->associated_be_is_disabled) {
+ /*
+ * Check local acls
+ * Keep in mind We don't have the entry for acl evaluation
+ */
+
+ char * errbuf=NULL;
+ Slapi_Entry *te = slapi_entry_alloc();
+ slapi_entry_set_dn(te,slapi_ch_strdup(dn));
+ rc = cb_access_allowed (pb, te, NULL, NULL, SLAPI_ACL_WRITE,&errbuf);
+ slapi_entry_free(te);
+
+ if ( rc != LDAP_SUCCESS ) {
+ cb_send_ldap_result( pb, rc, NULL, errbuf, 0, NULL );
+ slapi_ch_free((void **)&errbuf);
+ return -1;
+ }
+ }
+
+ /*
+ * Grab a connection handle
+ */
+
+ if ((rc = cb_get_connection(cb->pool,&ld,&cnx,NULL,&cnxerrbuf)) != LDAP_SUCCESS) {
+ cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL, cnxerrbuf, 0, NULL);
+ slapi_ch_free((void **)&cnxerrbuf);
+ /* ping the farm. If the farm is unreachable, we increment the counter */
+ cb_ping_farm(cb,NULL,0);
+ return -1;
+ }
+
+ /*
+ * Control management
+ */
+
+ if ( (rc = cb_update_controls( pb,ld,&ctrls,CB_UPDATE_CONTROLS_ADDAUTH )) != LDAP_SUCCESS ) {
+ cb_send_ldap_result( pb, rc, NULL,NULL, 0, NULL);
+ cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc));
+ return -1;
+ }
+
+
+ if ( slapi_op_abandoned( pb )) {
+ cb_release_op_connection(cb->pool,ld,0);
+ if ( NULL != ctrls)
+ ldap_controls_free(ctrls);
+ return -1;
+ }
+
+ /* heart-beat management */
+ if (cb->max_idle_time>0)
+ endtime=current_time() + cb->max_idle_time;
+
+ /*
+ * Send LDAP operation to the remote host
+ */
+
+ rc = ldap_rename ( ld,dn,newrdn,newsuperior,deleteoldrdn,ctrls,NULL,&msgid);
+
+ if ( NULL != ctrls)
+ ldap_controls_free(ctrls);
+ if ( rc != LDAP_SUCCESS ) {
+ cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL,
+ ldap_err2string(rc), 0, NULL);
+ cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc));
+ return -1;
+ }
+
+ while ( 1 ) {
+
+ if (cb_check_forward_abandon(cb,pb,ld,msgid)) {
+ return -1;
+ }
+
+ rc = ldap_result( ld, msgid, 0, &cb->abandon_timeout, &res );
+ switch ( rc ) {
+ case -1:
+ cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL,
+ ldap_err2string(rc), 0, NULL);
+ cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc));
+ if (res)
+ ldap_msgfree(res);
+ return -1;
+ case 0:
+ if ((rc=cb_ping_farm(cb,cnx,endtime)) != LDAP_SUCCESS) {
+
+ /* does not respond. give up and return a*/
+ /* error to the client. */
+
+ /*cb_send_ldap_result(pb,LDAP_OPERATIONS_ERROR, NULL,
+ ldap_err2string(rc), 0, NULL);*/
+ cb_send_ldap_result(pb,LDAP_OPERATIONS_ERROR, NULL, "FARM SERVER TEMPORARY UNAVAILABLE", 0, NULL);
+ cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc));
+ if (res)
+ ldap_msgfree(res);
+ return -1;
+ }
+#ifdef CB_YIELD
+ DS_Sleep(PR_INTERVAL_NO_WAIT);
+#endif
+ break;
+ default:
+ matched_msg=error_msg=NULL;
+ parse_rc = ldap_parse_result( ld, res, &rc, &matched_msg,
+ &error_msg, &referrals, &serverctrls, 1 );
+
+ if ( parse_rc != LDAP_SUCCESS ) {
+ cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL,
+ ldap_err2string(parse_rc), 0, NULL);
+ cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(parse_rc));
+ slapi_ch_free((void **)&matched_msg);
+ slapi_ch_free((void **)&error_msg);
+ if (serverctrls)
+ ldap_controls_free(serverctrls);
+ /* jarnou: free referrals */
+ if (referrals)
+ charray_free(referrals);
+ return -1;
+ }
+
+ if ( rc != LDAP_SUCCESS ) {
+ struct berval ** refs = referrals2berval(referrals);
+
+ cb_send_ldap_result( pb, rc, matched_msg, error_msg, 0, refs);
+ cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc));
+ slapi_ch_free((void **)&matched_msg);
+ slapi_ch_free((void **)&error_msg);
+ if (refs)
+ ber_bvecfree(refs);
+ if (referrals)
+ charray_free(referrals);
+ if (serverctrls)
+ ldap_controls_free(serverctrls);
+ return -1;
+ }
+
+ cb_release_op_connection(cb->pool,ld,0);
+
+ /* Add control response sent by the farm server */
+
+ for (i=0; serverctrls && serverctrls[i];i++)
+ slapi_pblock_set( pb, SLAPI_ADD_RESCONTROL, serverctrls[i]);
+ if (serverctrls)
+ ldap_controls_free(serverctrls);
+ /* jarnou: free matched_msg, error_msg, and referrals if necessary */
+ slapi_ch_free((void **)&matched_msg);
+ slapi_ch_free((void **)&error_msg);
+ if (referrals)
+ charray_free(referrals);
+ cb_send_ldap_result( pb, LDAP_SUCCESS, NULL, NULL, 0, NULL );
+ return 0;
+ }
+ }
+
+ /* Never reached */
+ /* return 0; */
+}
diff --git a/ldap/servers/plugins/chainingdb/cb_monitor.c b/ldap/servers/plugins/chainingdb/cb_monitor.c
new file mode 100644
index 00000000..11ef3bda
--- /dev/null
+++ b/ldap/servers/plugins/chainingdb/cb_monitor.c
@@ -0,0 +1,228 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include "cb.h"
+
+extern cb_instance_config_info cb_the_instance_config[];
+/*
+** Chaining backend instance monitor function
+** This function wraps up backend specific monitoring information
+** and return it to the client as an LDAP entry.
+** This function is usually called upon receipt of the monitor
+** dn for this backend
+**
+** Monitor information:
+**
+** Database misc
+** database
+**
+** Number of hits for each operation
+** addopcount
+** modifyopcount
+** deleteopcount
+** modrdnopcount
+** compareopcount
+** searchsubtreeopcount
+** searchonelevelopcount
+** searchbaseopcount
+** bindopcount
+** unbindopcount
+** abandonopcount
+**
+** Outgoing connections
+** outgoingopconnections
+** outgoingbindconnections
+**
+*/
+
+int
+cb_search_monitor_callback(Slapi_PBlock * pb, Slapi_Entry * e, Slapi_Entry * entryAfter, int * returnCode, char * returnText, void * arg)
+{
+
+ char buf[CB_BUFSIZE];
+ struct berval val;
+ struct berval *vals[2];
+ int deletecount,addcount,modifycount,modrdncount,searchbasecount,searchonelevelcount;
+ int searchsubtreecount,abandoncount,bindcount,unbindcount,comparecount;
+ int outgoingconn, outgoingbindconn;
+ cb_backend_instance *inst = (cb_backend_instance *)arg;
+
+ /* First make sure the backend instance is configured */
+ /* If not, don't return anything */
+
+ PR_RWLock_Rlock(inst->rwl_config_lock);
+ if (!inst->isconfigured) {
+ *returnCode= LDAP_NO_SUCH_OBJECT;
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ return SLAPI_DSE_CALLBACK_ERROR;
+ }
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+
+ vals[0] = &val;
+ vals[1] = NULL;
+
+ slapi_lock_mutex(inst->monitor.mutex);
+
+ addcount =inst->monitor.addcount;
+ deletecount =inst->monitor.deletecount;
+ modifycount =inst->monitor.modifycount;
+ modrdncount =inst->monitor.modrdncount;
+ searchbasecount =inst->monitor.searchbasecount;
+ searchonelevelcount =inst->monitor.searchonelevelcount;
+ searchsubtreecount =inst->monitor.searchsubtreecount;
+ abandoncount =inst->monitor.abandoncount;
+ bindcount =inst->monitor.bindcount;
+ unbindcount =inst->monitor.unbindcount;
+ comparecount =inst->monitor.comparecount;
+
+ slapi_unlock_mutex(inst->monitor.mutex);
+
+ /*
+ ** Get connection information
+ */
+
+ slapi_lock_mutex(inst->pool->conn.conn_list_mutex);
+ outgoingconn= inst->pool->conn.conn_list_count;
+ slapi_unlock_mutex(inst->pool->conn.conn_list_mutex);
+
+ slapi_lock_mutex(inst->bind_pool->conn.conn_list_mutex);
+ outgoingbindconn= inst->bind_pool->conn.conn_list_count;
+ slapi_unlock_mutex(inst->bind_pool->conn.conn_list_mutex);
+
+ sprintf( buf, "%lu", addcount );
+ val.bv_val = buf;
+ val.bv_len = strlen( buf );
+ slapi_entry_attr_replace( e, CB_MONITOR_ADDCOUNT, ( struct berval **)vals );
+
+ sprintf( buf, "%lu", deletecount );
+ val.bv_val = buf;
+ val.bv_len = strlen( buf );
+ slapi_entry_attr_replace( e, CB_MONITOR_DELETECOUNT, ( struct berval **)vals );
+
+ sprintf( buf, "%lu", modifycount );
+ val.bv_val = buf;
+ val.bv_len = strlen( buf );
+ slapi_entry_attr_replace( e, CB_MONITOR_MODIFYCOUNT, ( struct berval **)vals );
+
+ sprintf( buf, "%lu", modrdncount );
+ val.bv_val = buf;
+ val.bv_len = strlen( buf );
+ slapi_entry_attr_replace( e, CB_MONITOR_MODRDNCOUNT, ( struct berval **)vals );
+
+ sprintf( buf, "%lu", searchbasecount );
+ val.bv_val = buf;
+ val.bv_len = strlen( buf );
+ slapi_entry_attr_replace( e, CB_MONITOR_SEARCHBASECOUNT, ( struct berval **)vals );
+
+ sprintf( buf, "%lu", searchonelevelcount );
+ val.bv_val = buf;
+ val.bv_len = strlen( buf );
+ slapi_entry_attr_replace( e, CB_MONITOR_SEARCHONELEVELCOUNT, ( struct berval **)vals );
+
+ sprintf( buf, "%lu", searchsubtreecount );
+ val.bv_val = buf;
+ val.bv_len = strlen( buf );
+ slapi_entry_attr_replace( e, CB_MONITOR_SEARCHSUBTREECOUNT, ( struct berval **)vals );
+
+ sprintf( buf, "%lu", abandoncount );
+ val.bv_val = buf;
+ val.bv_len = strlen( buf );
+ slapi_entry_attr_replace( e, CB_MONITOR_ABANDONCOUNT, ( struct berval **)vals );
+
+ sprintf( buf, "%lu", bindcount );
+ val.bv_val = buf;
+ val.bv_len = strlen( buf );
+ slapi_entry_attr_replace( e, CB_MONITOR_BINDCOUNT, ( struct berval **)vals );
+
+ sprintf( buf, "%lu", unbindcount );
+ val.bv_val = buf;
+ val.bv_len = strlen( buf );
+ slapi_entry_attr_replace( e, CB_MONITOR_UNBINDCOUNT, ( struct berval **)vals );
+
+ sprintf( buf, "%lu", comparecount );
+ val.bv_val = buf;
+ val.bv_len = strlen( buf );
+ slapi_entry_attr_replace( e, CB_MONITOR_COMPARECOUNT, ( struct berval **)vals );
+
+ sprintf( buf, "%d", outgoingconn );
+ val.bv_val = buf;
+ val.bv_len = strlen( buf );
+ slapi_entry_attr_replace( e, CB_MONITOR_OUTGOINGCONN, ( struct berval **)vals );
+
+ sprintf( buf, "%d", outgoingbindconn );
+ val.bv_val = buf;
+ val.bv_len = strlen( buf );
+ slapi_entry_attr_replace( e, CB_MONITOR_OUTGOINGBINDCOUNT, ( struct berval **)vals );
+
+ *returnCode= LDAP_SUCCESS;
+ return(SLAPI_DSE_CALLBACK_OK);
+}
+
+void
+cb_update_monitor_info(Slapi_PBlock * pb, cb_backend_instance * inst,int op)
+{
+
+ int scope;
+
+ slapi_lock_mutex(inst->monitor.mutex);
+ switch (op) {
+ case SLAPI_OPERATION_ADD:
+ inst->monitor.addcount++;
+ break;
+ case SLAPI_OPERATION_MODIFY:
+ inst->monitor.modifycount++;
+ break;
+ case SLAPI_OPERATION_DELETE:
+ inst->monitor.deletecount++;
+ break;
+ case SLAPI_OPERATION_MODRDN:
+/** case SLAPI_OPERATION_MODDN: **/
+ inst->monitor.modrdncount++;
+ break;
+ case SLAPI_OPERATION_COMPARE:
+ inst->monitor.comparecount++;
+ break;
+ case SLAPI_OPERATION_ABANDON:
+ inst->monitor.abandoncount++;
+ break;
+ case SLAPI_OPERATION_BIND:
+ inst->monitor.bindcount++;
+ break;
+ case SLAPI_OPERATION_UNBIND:
+ inst->monitor.unbindcount++;
+ break;
+ case SLAPI_OPERATION_SEARCH:
+ slapi_pblock_get( pb, SLAPI_SEARCH_SCOPE, &scope );
+ if ( LDAP_SCOPE_BASE == scope )
+ inst->monitor.searchbasecount++;
+ else
+ if ( LDAP_SCOPE_ONELEVEL == scope )
+ inst->monitor.searchonelevelcount++;
+ else
+ inst->monitor.searchsubtreecount++;
+ break;
+ default:
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,"cb_update_monitor_info: invalid op type <%d>\n",op);
+ }
+ slapi_unlock_mutex(inst->monitor.mutex);
+}
+
+
+int
+cb_delete_monitor_callback(Slapi_PBlock * pb, Slapi_Entry * e, Slapi_Entry * entryAfter, int * returnCode, char * returnText, void * arg)
+{
+
+ cb_backend_instance *inst = (cb_backend_instance *)arg;
+
+ slapi_config_remove_callback(SLAPI_OPERATION_SEARCH, DSE_FLAG_PREOP, inst->monitorDn, LDAP_SCOPE_BASE,
+ "(objectclass=*)", cb_search_monitor_callback);
+ slapi_config_remove_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_PREOP, inst->monitorDn, LDAP_SCOPE_BASE,
+ "(objectclass=*)", cb_dont_allow_that);
+ slapi_config_remove_callback(SLAPI_OPERATION_DELETE, DSE_FLAG_PREOP, inst->monitorDn, LDAP_SCOPE_BASE,
+ "(objectclass=*)", cb_delete_monitor_callback);
+
+ *returnCode= LDAP_SUCCESS;
+ return(SLAPI_DSE_CALLBACK_OK);
+}
diff --git a/ldap/servers/plugins/chainingdb/cb_schema.c b/ldap/servers/plugins/chainingdb/cb_schema.c
new file mode 100644
index 00000000..2337b977
--- /dev/null
+++ b/ldap/servers/plugins/chainingdb/cb_schema.c
@@ -0,0 +1,45 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include "cb.h"
+
+void cb_eliminate_illegal_attributes(cb_backend_instance * inst, Slapi_Entry * e) {
+
+ /* get rid of illegal attributes before sending op to the */
+ /* farm server. (Add) */
+
+ int rc,j;
+ Slapi_Attr *attr=NULL;
+ char *tobefreed=NULL;
+
+ if (inst->illegal_attributes != NULL ) { /* Unlikely to happen */
+
+ PR_RWLock_Wlock(inst->rwl_config_lock);
+
+ for (j=0; inst->illegal_attributes[j]; j++) {
+ char * aType=NULL;
+ rc=slapi_entry_first_attr(e,&attr);
+ while (rc==0) {
+ if (tobefreed) {
+ slapi_entry_attr_delete( e, tobefreed);
+ tobefreed=NULL;
+ }
+ slapi_attr_get_type(attr,&aType);
+ if (aType && slapi_attr_types_equivalent(inst->illegal_attributes[j],aType)) {
+ tobefreed=aType;
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "attribute <%s> not forwarded.\n",aType);
+ }
+ rc = slapi_entry_next_attr(e, attr, &attr);
+ }
+ if (tobefreed) {
+ slapi_entry_attr_delete( e, tobefreed);
+ tobefreed=NULL;
+ }
+ }
+
+ PR_RWLock_Unlock(inst->rwl_config_lock);
+ }
+}
diff --git a/ldap/servers/plugins/chainingdb/cb_search.c b/ldap/servers/plugins/chainingdb/cb_search.c
new file mode 100644
index 00000000..d9cf6ef7
--- /dev/null
+++ b/ldap/servers/plugins/chainingdb/cb_search.c
@@ -0,0 +1,698 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include "cb.h"
+
+/*
+ * Build a candidate list for this backentry and scope.
+ * Could be a BASE, ONELEVEL, or SUBTREE search.
+ *
+ * Returns:
+ * 0 - success
+ * <0 - fail
+ *
+ */
+
+int
+chainingdb_build_candidate_list ( Slapi_PBlock *pb )
+{
+
+ Slapi_Backend * be;
+ Slapi_Operation * op;
+ char *target, *filter;
+ int scope,attrsonly,sizelimit,timelimit,rc,searchreferral;
+ char **attrs=NULL;
+ LDAPControl **controls=NULL;
+ LDAPControl **ctrls=NULL;
+ LDAP *ld=NULL;
+ cb_backend_instance *cb = NULL;
+ cb_searchContext *ctx=NULL;
+ struct timeval timeout;
+ time_t optime;
+ int doit,parse_rc;
+ LDAPMessage *res=NULL;
+ char *matched_msg,*error_msg;
+ LDAPControl **serverctrls=NULL;
+ char **referrals=NULL;
+ char *cnxerrbuf=NULL;
+ time_t endbefore=0;
+ time_t endtime;
+ cb_outgoing_conn *cnx;
+
+ slapi_pblock_get( pb, SLAPI_BACKEND, &be );
+ cb = cb_get_instance(be);
+
+ slapi_pblock_get( pb, SLAPI_OPERATION, &op );
+ slapi_pblock_get( pb, SLAPI_SEARCH_STRFILTER, &filter );
+ slapi_pblock_get( pb, SLAPI_SEARCH_SCOPE, &scope );
+ slapi_pblock_get( pb, SLAPI_OPINITIATED_TIME, &optime );
+ slapi_pblock_get( pb, SLAPI_SEARCH_TARGET, &target );
+
+ if ( LDAP_SUCCESS != (parse_rc=cb_forward_operation(pb) )) {
+
+ /* Don't return errors */
+
+ if (cb_debug_on()) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "local search: base:<%s> scope:<%s> filter:<%s>\n",target,
+ scope==LDAP_SCOPE_SUBTREE?"SUBTREE":scope==LDAP_SCOPE_ONELEVEL ? "ONE-LEVEL" : "BASE" , filter);
+ }
+
+ ctx = (cb_searchContext *)slapi_ch_calloc(1,sizeof(cb_searchContext));
+ ctx->type = CB_SEARCHCONTEXT_ENTRY;
+ ctx->data=NULL;
+
+ slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_SET,ctx);
+ return 0;
+ }
+
+ cb_update_monitor_info(pb,cb,SLAPI_OPERATION_SEARCH);
+
+ /* Check wether the chaining BE is available or not */
+ if ( cb_check_availability( cb, pb ) == FARMSERVER_UNAVAILABLE ){
+ return -1;
+ }
+
+ if (cb_debug_on()) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "chained search: base:<%s> scope:<%s> filter:<%s>\n",target,
+ scope==LDAP_SCOPE_SUBTREE?"SUBTREE":scope==LDAP_SCOPE_ONELEVEL ? "ONE-LEVEL" : "BASE" , filter);
+ }
+
+ slapi_pblock_get( pb, SLAPI_SEARCH_ATTRS, &attrs );
+ slapi_pblock_get( pb, SLAPI_SEARCH_ATTRSONLY, &attrsonly );
+ slapi_pblock_get( pb, SLAPI_REQCONTROLS, &controls );
+ slapi_pblock_get( pb, SLAPI_SEARCH_TIMELIMIT, &timelimit );
+ slapi_pblock_get( pb, SLAPI_SEARCH_SIZELIMIT, &sizelimit );
+ slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_SET,NULL);
+
+
+ if ((scope != LDAP_SCOPE_BASE) && (scope != LDAP_SCOPE_ONELEVEL) && (scope != LDAP_SCOPE_SUBTREE)) {
+ cb_send_ldap_result( pb, LDAP_PROTOCOL_ERROR, NULL, "Bad scope", 0, NULL );
+ return 1;
+ }
+
+ searchreferral=cb->searchreferral;
+
+ if (( scope != LDAP_SCOPE_BASE ) && ( searchreferral )) {
+
+ int i;
+ struct berval bv,*bvals[2];
+ Slapi_Entry ** aciArray=(Slapi_Entry **) slapi_ch_malloc(2*sizeof(Slapi_Entry *));
+ Slapi_Entry *anEntry = slapi_entry_alloc();
+
+ slapi_entry_set_dn(anEntry,slapi_ch_strdup(target));
+
+ bvals[1]=NULL;
+ bvals[0]=&bv;
+ bv.bv_val="referral";
+ bv.bv_len=strlen(bv.bv_val);
+ slapi_entry_add_values( anEntry, "objectclass", bvals);
+
+ PR_RWLock_Rlock(cb->rwl_config_lock);
+ for (i=0; cb->url_array && cb->url_array[i]; i++) {
+ char * anUrl= slapi_ch_calloc(1,strlen(cb->url_array[i])+strlen(target)+1);
+ sprintf(anUrl,"%s%s",cb->url_array[i],target);
+ bv.bv_val=anUrl;
+ bv.bv_len=strlen(bv.bv_val);
+ slapi_entry_attr_merge( anEntry, "ref", bvals);
+ slapi_ch_free((void **)&anUrl);
+ }
+ PR_RWLock_Unlock(cb->rwl_config_lock);
+
+ aciArray[0]=anEntry;
+ aciArray[1]=NULL;
+
+ ctx = (cb_searchContext *)slapi_ch_calloc(1,sizeof(cb_searchContext));
+ ctx->type = CB_SEARCHCONTEXT_ENTRY;
+ ctx->data=aciArray;
+
+ slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_SET,ctx);
+ return 0;
+ }
+
+ /*
+ ** Time limit management.
+ ** Make sure the operation has not expired
+ */
+
+ if ( timelimit == -1 ) {
+ timeout.tv_sec = timeout.tv_usec = 0;
+ } else {
+ time_t now=current_time();
+ endbefore=optime + timelimit;
+ if (now >= endbefore) {
+ cb_send_ldap_result( pb, LDAP_TIMELIMIT_EXCEEDED, NULL,NULL, 0, NULL);
+ slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_ENTRY, NULL );
+ return 1;
+ }
+ timeout.tv_sec=timelimit-(now-optime);
+ timeout.tv_usec=0;
+ }
+
+ /* Operational attribute support for internal searches: */
+ /* The front-end relies on the fact that operational attributes */
+ /* are returned along with standard attrs when the attr list is */
+ /* NULL. To make it work, we need to explicitly request for all*/
+ /* possible operational attrs. Too bad. */
+
+ if ( (attrs == NULL) && operation_is_flag_set(op, OP_FLAG_INTERNAL) ) {
+ attrs = cb->every_attribute;
+
+ }
+ else
+ {
+ int i;
+ if ( attrs != NULL )
+ {
+ for ( i = 0; attrs[i] != NULL; i++ ) {
+ if ( strcasecmp( "nsrole", attrs[i] ) == 0 )
+ {
+ attrs = cb->every_attribute;
+ break;
+ }
+ }
+ }
+ }
+
+ /* Grab a connection handle */
+
+ if ( LDAP_SUCCESS != (rc = cb_get_connection(cb->pool,&ld,&cnx,&timeout,&cnxerrbuf))) {
+ if (rc == LDAP_TIMELIMIT_EXCEEDED)
+ cb_send_ldap_result( pb, rc, NULL,cnxerrbuf, 0, NULL);
+ else
+ cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL,cnxerrbuf, 0, NULL);
+
+ slapi_ch_free((void **)&cnxerrbuf);
+ /* ping the farm. If the farm is unreachable, we increment the counter */
+ cb_ping_farm(cb,NULL,0);
+ return 1;
+ }
+
+ /*
+ * Control management
+ */
+
+ if ( LDAP_SUCCESS != (rc = cb_update_controls( pb,ld,&ctrls,CB_UPDATE_CONTROLS_ADDAUTH ))) {
+ cb_send_ldap_result( pb, rc, NULL,NULL, 0, NULL);
+ cb_release_op_connection(cb->pool,ld,0);
+ return 1;
+ }
+
+ if ( slapi_op_abandoned( pb )) {
+ cb_release_op_connection(cb->pool,ld,0);
+ if ( NULL != ctrls)
+ ldap_controls_free(ctrls);
+ return 1;
+ }
+
+ ctx = (cb_searchContext *) slapi_ch_calloc(1,sizeof(cb_searchContext));
+
+ /*
+ ** We need to store the connection handle in the search context
+ ** to make sure we reuse it in the next_entry iteration
+ ** Indeed, if another thread on this connection detects a problem
+ ** on this connection, it may reallocate a new connection and
+ ** a call to get_connection may return a new cnx. Too bad.
+ */
+
+ ctx->ld=ld;
+ ctx->cnx=cnx;
+
+ /* for some reasons, it is an error to pass in a zero'd timeval */
+ /* to ldap_search_ext() */
+ if ((timeout.tv_sec==0) && (timeout.tv_usec==0))
+ timeout.tv_sec=timeout.tv_usec=-1;
+
+ /* heart-beat management */
+ if (cb->max_idle_time>0)
+ endtime=current_time() + cb->max_idle_time;
+
+ rc=ldap_search_ext(ld ,target,scope,filter,attrs,attrsonly,
+ ctrls, NULL, &timeout,sizelimit, &(ctx->msgid) );
+
+ if ( NULL != ctrls)
+ ldap_controls_free(ctrls);
+
+ if ( LDAP_SUCCESS != rc ) {
+ cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL, ldap_err2string(rc), 0, NULL);
+ cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc));
+ slapi_ch_free((void **) &ctx);
+ return 1;
+ }
+
+ /*
+ ** Need to get the very first result to handle
+ ** errors properly, especially no search base.
+ */
+
+ doit=1;
+ while (doit) {
+
+ if (cb_check_forward_abandon(cb,pb,ctx->ld,ctx->msgid)) {
+ slapi_ch_free((void **) &ctx);
+ return 1;
+ }
+
+ rc=ldap_result(ld,ctx->msgid,LDAP_MSG_ONE,&cb->abandon_timeout,&res);
+ switch ( rc ) {
+ case -1:
+ /* An error occurred. return now */
+ rc = ldap_get_lderrno(ld,NULL,NULL);
+ /* tuck away some errors in a OPERATION_ERROR */
+ if (CB_LDAP_CONN_ERROR(rc)) {
+ cb_send_ldap_result(pb,LDAP_OPERATIONS_ERROR, NULL,
+ ldap_err2string( rc ), 0, NULL);
+ } else {
+ cb_send_ldap_result(pb,rc, NULL, NULL,0,NULL);
+ }
+ cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc));
+ if (res)
+ ldap_msgfree(res);
+ slapi_ch_free((void **)&ctx);
+ return 1;
+ case 0:
+
+ /* Local timeout management */
+ if (timelimit != -1) {
+ if (current_time() > endbefore) {
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "Local timeout expiration\n");
+
+ cb_send_ldap_result(pb,LDAP_TIMELIMIT_EXCEEDED,
+ NULL,NULL, 0, NULL);
+ /* Force connection close */
+ cb_release_op_connection(cb->pool,ld,1);
+ if (res)
+ ldap_msgfree(res);
+ slapi_ch_free((void **)&ctx);
+ return 1;
+ }
+ }
+ /* heart-beat management */
+ if ((rc=cb_ping_farm(cb,cnx,endtime)) != LDAP_SUCCESS) {
+ cb_send_ldap_result(pb,LDAP_OPERATIONS_ERROR, NULL,
+ ldap_err2string(rc), 0, NULL);
+ cb_release_op_connection(cb->pool,ld,CB_LDAP_CONN_ERROR(rc));
+ if (res)
+ ldap_msgfree(res);
+ slapi_ch_free((void **)&ctx);
+ return 1;
+ }
+
+#ifdef CB_YIELD
+ DS_Sleep(PR_INTERVAL_NO_WAIT);
+#endif
+ break;
+ case LDAP_RES_SEARCH_ENTRY:
+ case LDAP_RES_SEARCH_REFERENCE:
+ /* Some results received */
+ /* don't parse result here */
+ ctx->pending_result=res;
+ ctx->pending_result_type=rc;
+ doit=0;
+ break;
+ case LDAP_RES_SEARCH_RESULT:
+ matched_msg=NULL;
+ error_msg=NULL;
+ referrals=NULL;
+ serverctrls=NULL;
+ parse_rc=ldap_parse_result(ld,res,&rc,&matched_msg,
+ &error_msg,&referrals, &serverctrls, 0 );
+ if ( parse_rc != LDAP_SUCCESS ) {
+ cb_send_ldap_result(pb,parse_rc,
+ matched_msg,error_msg,0,NULL);
+ rc=-1;
+ } else
+ if ( rc != LDAP_SUCCESS ) {
+ ldap_get_lderrno( ctx->ld, &matched_msg, &error_msg );
+ cb_send_ldap_result( pb, rc, matched_msg,
+ error_msg,0,NULL);
+ /* BEWARE: matched_msg and error_msg points */
+ /* to ld fields. */
+ matched_msg=NULL;
+ error_msg=NULL;
+ rc=-1;
+ }
+
+ slapi_ch_free((void **)&matched_msg);
+ slapi_ch_free((void **)&error_msg);
+ if (serverctrls)
+ ldap_controls_free(serverctrls);
+ if (referrals)
+ charray_free(referrals);
+
+ if (rc!=LDAP_SUCCESS) {
+ cb_release_op_connection(cb->pool,ld,
+ CB_LDAP_CONN_ERROR(rc));
+ ldap_msgfree(res);
+ slapi_ch_free((void **)&ctx);
+ return -1;
+ }
+
+ /* Store the msg in the ctx */
+ /* Parsed in iterate. */
+
+ ctx->pending_result=res;
+ ctx->pending_result_type=LDAP_RES_SEARCH_RESULT;
+ doit=0;
+ }
+ }
+
+ slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_SET,ctx);
+ return 0;
+}
+
+/*
+ * Return the next entry in the result set. The entry is returned
+ * in the pblock.
+ * Returns 0 normally. If -1 is returned, it means that some
+ * exceptional condition, e.g. timelimit exceeded has occurred,
+ * and this routine has sent a result to the client. If zero
+ * is returned and no entry is available in the PBlock, then
+ * we've iterated through all the entries.
+ */
+
+int
+chainingdb_next_search_entry ( Slapi_PBlock *pb )
+{
+
+ char *target;
+ int sizelimit,timelimit, rc, parse_rc, optime,i,retcode, attrsonly;
+ LDAPMessage *res=NULL;
+ char *matched_msg,*error_msg;
+ cb_searchContext *ctx=NULL;
+ Slapi_Entry *entry;
+ LDAPControl **serverctrls=NULL;
+ char **referrals=NULL;
+ cb_backend_instance * cb=NULL;
+ Slapi_Backend * be;
+ time_t endtime;
+
+ matched_msg=error_msg=NULL;
+
+ slapi_pblock_get( pb, SLAPI_SEARCH_RESULT_SET, &ctx );
+ slapi_pblock_get( pb, SLAPI_BACKEND, &be );
+ slapi_pblock_get( pb, SLAPI_SEARCH_TIMELIMIT, &timelimit );
+ slapi_pblock_get( pb, SLAPI_SEARCH_SIZELIMIT, &sizelimit );
+ slapi_pblock_get( pb, SLAPI_SEARCH_TARGET, &target );
+ slapi_pblock_get( pb, SLAPI_OPINITIATED_TIME, &optime );
+ slapi_pblock_get( pb, SLAPI_SEARCH_ATTRSONLY, &attrsonly );
+
+ cb = cb_get_instance(be);
+
+ if ( NULL == ctx ) {
+ /* End of local search */
+ slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_SET,NULL);
+ slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_ENTRY,NULL);
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "Unexpected NULL ctx in chainingdb_next_search_entry\n");
+ return 0;
+ }
+
+ if ( NULL != ctx->tobefreed ) {
+ slapi_entry_free(ctx->tobefreed);
+ ctx->tobefreed=NULL;
+ }
+
+ if ( ctx->type == CB_SEARCHCONTEXT_ENTRY ) {
+
+ int n;
+ Slapi_Entry ** ptr;
+ if ( (timelimit != -1) && (timelimit != 0)) {
+ time_t now=current_time();
+
+ if (now > (optime + timelimit)) {
+ cb_send_ldap_result( pb, LDAP_TIMELIMIT_EXCEEDED, NULL,NULL, 0, NULL);
+ slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_SET,NULL );
+ slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_ENTRY,NULL);
+
+ for ( n = 0, ptr=(Slapi_Entry **)ctx->data; ptr != NULL && ptr[n] != NULL; n++ ) {
+ slapi_entry_free(ptr[n]);
+ }
+ if (ctx->data)
+ slapi_ch_free((void **)&ctx->data);
+ slapi_ch_free((void **)&ctx);
+ return -1;
+ }
+ }
+
+ /*
+ ** Return the Slapi_Entry of the result set one
+ ** by one
+ */
+
+ for ( n = 0, ptr=(Slapi_Entry **)ctx->data; ptr != NULL && ptr[n] != NULL; n++ );
+ if ( n != 0) {
+ Slapi_Entry * anEntry=ptr[n-1];
+ ptr[n-1]=NULL;
+ slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_ENTRY,anEntry);
+ slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_SET,ctx);
+ cb_set_acl_policy(pb);
+ ctx->tobefreed=anEntry;
+ } else {
+ slapi_ch_free((void **) &ctx);
+ slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_SET,NULL );
+ slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_ENTRY,NULL);
+ }
+ return 0;
+ }
+
+ /*
+ * Grab a connection handle. Should be the same as the one
+ * used in the build_candidate list. To be certain of that, grab it from
+ * the context.
+ */
+
+ /* Poll the server for the results of the search operation.
+ * Passing LDAP_MSG_ONE indicates that you want to receive
+ * the entries one at a time, as they come in. If the next
+ * entry that you retrieve is NULL, there are no more entries.
+ */
+
+ /* heart-beat management */
+ if (cb->max_idle_time>0)
+ endtime=current_time() + cb->max_idle_time;
+
+ while (1) {
+
+ if (cb_check_forward_abandon(cb,pb,ctx->ld,ctx->msgid)) {
+ /* cnx handle released */
+ if (ctx->pending_result)
+ ldap_msgfree(ctx->pending_result);
+ slapi_ch_free((void **) &ctx);
+ slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_SET,NULL );
+ slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_ENTRY,NULL);
+ return -1;
+ }
+
+ /* Check for time limit done by the remote farm server */
+ /* Check for size limit done by the remote farm server */
+
+ /* Use pending msg if one is available */
+ if (ctx->pending_result) {
+ res=ctx->pending_result;
+ rc=ctx->pending_result_type;
+ ctx->pending_result=NULL;
+ } else {
+
+
+ rc=ldap_result(ctx->ld,ctx->msgid,
+ LDAP_MSG_ONE, &cb->abandon_timeout, &res );
+ }
+
+ /* The server can return three types of results back to the client,
+ * and the return value of ldap_result() indicates the result type:
+ * LDAP_RES_SEARCH_ENTRY identifies an entry found by the search,
+ * LDAP_RES_SEARCH_REFERENCE identifies a search reference returned
+ * by the server, and LDAP_RES_SEARCH_RESULT is the last result
+ * sent from the server to the client after the operation completes.
+ * We need to check for each of these types of results.
+ */
+
+ switch ( rc ) {
+ case -1:
+
+ /* An error occurred. */
+ rc = ldap_get_lderrno( ctx->ld, NULL, NULL );
+ slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_SET,NULL);
+ slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_ENTRY,NULL);
+
+ cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL, ldap_err2string( rc ), 0, NULL);
+
+ if (res)
+ ldap_msgfree(res);
+ cb_release_op_connection(cb->pool,ctx->ld,CB_LDAP_CONN_ERROR(rc));
+ slapi_ch_free((void **)&ctx);
+ return -1;
+ case 0:
+ /* heart-beat management */
+ if ((rc=cb_ping_farm(cb,ctx->cnx,endtime)) != LDAP_SUCCESS) {
+
+ slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_SET,NULL);
+ slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_ENTRY,NULL);
+
+ cb_send_ldap_result(pb,LDAP_OPERATIONS_ERROR, NULL,
+ ldap_err2string(rc), 0, NULL);
+
+ if (res)
+ ldap_msgfree(res);
+ cb_release_op_connection(cb->pool,ctx->ld,CB_LDAP_CONN_ERROR(rc));
+ slapi_ch_free((void **)&ctx);
+ return -1;
+ }
+#ifdef CB_YIELD
+ DS_Sleep(PR_INTERVAL_NO_WAIT);
+#endif
+ break;
+
+ case LDAP_RES_SEARCH_ENTRY:
+
+ /* heart-beat management */
+ if (cb->max_idle_time>0)
+ endtime=current_time() + cb->max_idle_time;
+
+ /* The server sent one of the entries found by the search */
+ if ((entry = cb_LDAPMessage2Entry(ctx->ld,res,attrsonly)) == NULL) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,"Invalid entry received.\n");
+ slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_SET,NULL);
+ slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_ENTRY,NULL);
+
+ cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL, NULL , 0, NULL);
+
+ ldap_msgfree(res);
+ cb_release_op_connection(cb->pool,ctx->ld,0);
+ slapi_ch_free((void **)&ctx);
+ return -1;
+ }
+
+ ctx->tobefreed=entry;
+ slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_SET,ctx);
+ slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_ENTRY,entry);
+ cb_set_acl_policy(pb);
+ ldap_msgfree(res);
+ return 0;
+
+ case LDAP_RES_SEARCH_REFERENCE:
+
+ /* The server sent a search reference encountered during the
+ * search operation.
+ */
+
+ /* heart-beat management */
+ if (cb->max_idle_time>0)
+ endtime=current_time() + cb->max_idle_time;
+
+ parse_rc = ldap_parse_reference( ctx->ld, res, &referrals, NULL, 1 );
+ if ( parse_rc != LDAP_SUCCESS ) {
+ cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL,
+ ldap_err2string( parse_rc ), 0, NULL);
+ cb_release_op_connection(cb->pool,ctx->ld,CB_LDAP_CONN_ERROR(parse_rc));
+ slapi_ch_free((void **)&ctx);
+
+ slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_SET,NULL);
+ slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_ENTRY,NULL);
+ return -1;
+ }
+
+ /*
+ ** build a dummy entry on the fly with a ref attribute
+ */
+
+ {
+
+ struct berval bv;
+ int i;
+ struct berval *bvals[2];
+ Slapi_Entry *anEntry = slapi_entry_alloc();
+ slapi_entry_set_dn(anEntry,slapi_ch_strdup(target));
+
+ bvals[1]=NULL;
+ bvals[0]=&bv;
+
+ bv.bv_val="referral";
+ bv.bv_len=strlen(bv.bv_val);
+ slapi_entry_add_values( anEntry, "objectclass", bvals);
+
+ for (i=0;referrals[i] != NULL; i++) {
+ bv.bv_val=referrals[i];
+ bv.bv_len=strlen(bv.bv_val);
+ slapi_entry_add_values( anEntry, "ref", bvals);
+ }
+
+ slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_SET,ctx);
+ slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_ENTRY,anEntry);
+ cb_set_acl_policy(pb);
+ }
+
+ if (referrals != NULL) {
+ ldap_value_free( referrals );
+ }
+
+ return 0;
+
+ case LDAP_RES_SEARCH_RESULT:
+
+ /* Parse the final result received from the server. Note the last
+ * argument is a non-zero value, which indicates that the
+ * LDAPMessage structure will be freed when done.
+ */
+
+ slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_SET,NULL);
+ slapi_pblock_set( pb, SLAPI_SEARCH_RESULT_ENTRY,NULL);
+
+ parse_rc = ldap_parse_result( ctx->ld, res,
+ &rc,&matched_msg,&error_msg, &referrals, &serverctrls, 1 );
+ if ( parse_rc != LDAP_SUCCESS ) {
+ cb_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, matched_msg,
+ ldap_err2string( parse_rc ), 0, NULL);
+
+ retcode=-1;
+ } else
+ if ( rc != LDAP_SUCCESS ) {
+ ldap_get_lderrno( ctx->ld, &matched_msg, &error_msg );
+ cb_send_ldap_result( pb, rc, matched_msg, NULL, 0, NULL);
+
+ /* BEWARE: Don't free matched_msg && error_msg */
+ /* Points to the ld fields */
+ matched_msg=NULL;
+ error_msg=NULL;
+ retcode=-1;
+ } else {
+ /* Add control response sent by the farm server */
+ for (i=0; serverctrls && serverctrls[i];i++)
+ slapi_pblock_set( pb, SLAPI_ADD_RESCONTROL, serverctrls[i]);
+ retcode=0;
+ }
+
+ if (serverctrls)
+ ldap_controls_free(serverctrls);
+ slapi_ch_free((void **)&matched_msg);
+ slapi_ch_free((void **)&error_msg);
+ if (referrals)
+ charray_free(referrals);
+
+ cb_release_op_connection(cb->pool,ctx->ld,0);
+ slapi_ch_free((void **)&ctx);
+ return retcode;
+
+ default:
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "chainingdb_next_search_entry:default case.\n");
+
+ }
+ }
+
+ /* Not reached */
+ /* return 0; */
+}
+
+int
+chaining_back_entry_release ( Slapi_PBlock *pb ) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM, "chaining_back_entry_release\n");
+ return 0;
+}
+
diff --git a/ldap/servers/plugins/chainingdb/cb_size.c b/ldap/servers/plugins/chainingdb/cb_size.c
new file mode 100644
index 00000000..a6f1fb20
--- /dev/null
+++ b/ldap/servers/plugins/chainingdb/cb_size.c
@@ -0,0 +1,22 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include "cb.h"
+
+int
+cb_db_size( Slapi_PBlock *pb )
+{
+
+ /*
+ ** Return the size in byte of the local database storage
+ ** Size is 0 for a chaining backend
+ */
+
+ unsigned int size=0;
+
+ slapi_pblock_set( pb, SLAPI_DBSIZE, &size );
+ return 0;
+}
+
diff --git a/ldap/servers/plugins/chainingdb/cb_start.c b/ldap/servers/plugins/chainingdb/cb_start.c
new file mode 100644
index 00000000..c4a4538b
--- /dev/null
+++ b/ldap/servers/plugins/chainingdb/cb_start.c
@@ -0,0 +1,43 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include "cb.h"
+
+int
+chainingdb_start ( Slapi_PBlock *pb ) {
+
+ cb_backend * cb;
+
+ slapi_pblock_get( pb, SLAPI_PLUGIN_PRIVATE, &cb );
+
+ if (cb->started) {
+ /* We may be called multiple times due to */
+ /* plugin dependency resolution */
+ return 0;
+ }
+
+ /*
+ ** Reads in any configuration information held in the dse for the
+ ** chaining plugin. Create dse entries used to configure the
+ ** chaining plugin if they don't exist. Registers plugins to maintain
+ ** those dse entries.
+ */
+
+ cb_config_load_dse_info(pb);
+
+ /* Register new LDAPv3 controls supported by the chaining backend */
+
+ slapi_register_supported_control( CB_LDAP_CONTROL_CHAIN_SERVER,
+ SLAPI_OPERATION_SEARCH | SLAPI_OPERATION_COMPARE
+ | SLAPI_OPERATION_ADD | SLAPI_OPERATION_DELETE
+ | SLAPI_OPERATION_MODIFY | SLAPI_OPERATION_MODDN );
+
+ /* register to be notified when backend state changes */
+ slapi_register_backend_state_change((void *)cb_be_state_change,
+ cb_be_state_change);
+
+ cb->started=1;
+ return 0;
+}
diff --git a/ldap/servers/plugins/chainingdb/cb_temp.c b/ldap/servers/plugins/chainingdb/cb_temp.c
new file mode 100644
index 00000000..fc5407ff
--- /dev/null
+++ b/ldap/servers/plugins/chainingdb/cb_temp.c
@@ -0,0 +1,15 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include "cb.h"
+
+/*
+** Temp wrappers until the appropriate functions
+** are implemented in the slapi interface
+*/
+
+cb_backend_instance * cb_get_instance(Slapi_Backend * be) {
+ return (cb_backend_instance *)slapi_be_get_instance_info(be);
+}
diff --git a/ldap/servers/plugins/chainingdb/cb_test.c b/ldap/servers/plugins/chainingdb/cb_test.c
new file mode 100644
index 00000000..cb075664
--- /dev/null
+++ b/ldap/servers/plugins/chainingdb/cb_test.c
@@ -0,0 +1,76 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include "cb.h"
+
+int cb_back_test( Slapi_PBlock *pb )
+{
+
+ Slapi_Backend * be;
+ cb_backend * cb;
+ cb_backend_instance * inst;
+ Slapi_PBlock * apb;
+ int res;
+ int rc=0;
+ const Slapi_DN *aSuffix=NULL;
+ const char * aSuffixString;
+ char * theTarget;
+
+
+ slapi_pblock_get( pb, SLAPI_PLUGIN_PRIVATE, &cb );
+ slapi_pblock_get( pb, SLAPI_BACKEND, &be );
+ inst = cb_get_instance(be);
+ apb = slapi_pblock_new();
+
+ /*
+ ** Try to open a connection to the farm server
+ ** Try to get a dummy entry BELOW the suffix managed
+ ** by the chaining backend, in case the local root is shared
+ ** across different backend
+ */
+
+ printf("Begin test instance %s.\n",inst->inst_name);
+
+ aSuffix = slapi_be_getsuffix(be,0);
+ aSuffixString=slapi_sdn_get_dn(aSuffix);
+ /* Remove leading white spaces */
+ for (aSuffixString; *aSuffixString==' ';aSuffixString++) {}
+ theTarget=slapi_ch_calloc(1,strlen(aSuffixString)+20);
+ sprintf(theTarget,"cn=test,%s",aSuffixString);
+
+ /* XXXSD make sure chaining allowed for this plugin... */
+ slapi_search_internal_set_pb (apb, theTarget, LDAP_SCOPE_BASE, "objectclass=*", NULL, 0, NULL, NULL,
+ cb->identity,0 );
+ slapi_search_internal_pb (apb);
+
+ slapi_ch_free((void **)&theTarget);
+
+ if ( NULL == apb ) {
+ printf("Can't contact farm server. (Internal error).\n");
+ rc=-1;
+ goto the_end;
+ }
+
+ slapi_pblock_get(apb, SLAPI_PLUGIN_INTOP_RESULT, &res);
+ /* OPERATIONS ERRORS also returned when bind failed */
+ if (CB_LDAP_CONN_ERROR(res) || (res==LDAP_OPERATIONS_ERROR ))
+ {
+ printf("Can't contact the remote farm server %s. (%s).\n",inst->pool->hostname,ldap_err2string(res));
+ rc=-1;
+ goto the_end;
+ } else {
+ printf("Connection established with the remote farm server %s.\n",inst->pool->hostname);
+ }
+
+the_end:
+ if (apb)
+ {
+ slapi_free_search_results_internal(apb);
+ slapi_pblock_destroy (apb);
+ }
+
+ return rc;
+}
+
diff --git a/ldap/servers/plugins/chainingdb/cb_unbind.c b/ldap/servers/plugins/chainingdb/cb_unbind.c
new file mode 100644
index 00000000..1400ae1f
--- /dev/null
+++ b/ldap/servers/plugins/chainingdb/cb_unbind.c
@@ -0,0 +1,23 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include "cb.h"
+
+int
+chainingdb_unbind( Slapi_PBlock *pb ) {
+
+ /* Nothing to do because connection mgmt is stateless*/
+
+ Slapi_Backend * be;
+ cb_backend_instance * cb;
+
+ slapi_pblock_get( pb, SLAPI_BACKEND, &be );
+ cb = cb_get_instance(be);
+
+ cb_update_monitor_info(pb,cb,SLAPI_OPERATION_UNBIND);
+
+ cb_send_ldap_result( pb, LDAP_SUCCESS, NULL, NULL, 0, NULL );
+ return SLAPI_BIND_SUCCESS;
+}
diff --git a/ldap/servers/plugins/chainingdb/cb_utils.c b/ldap/servers/plugins/chainingdb/cb_utils.c
new file mode 100644
index 00000000..b73effd0
--- /dev/null
+++ b/ldap/servers/plugins/chainingdb/cb_utils.c
@@ -0,0 +1,375 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include "cb.h"
+
+/* return the rootdn configured in the server */
+
+char * cb_get_rootdn() {
+
+ char * ret=slapi_get_rootdn();
+ if (ret == NULL)
+ ret = slapi_ch_strdup(CB_DIRECTORY_MANAGER_DN);
+ if (ret)
+ slapi_dn_normalize_case(ret); /* UTF8-aware */
+ return ret;
+}
+
+void
+cb_send_ldap_result(Slapi_PBlock *pb, int err, char *matched,char *text, int nentries, struct berval **urls )
+{
+ cb_set_acl_policy(pb);
+ slapi_send_ldap_result( pb, err, matched, text, nentries ,urls);
+}
+
+Slapi_Entry * cb_LDAPMessage2Entry(LDAP * ld, LDAPMessage * msg, int attrsonly) {
+
+ Slapi_Entry * e = slapi_entry_alloc();
+ char * a=NULL;
+ BerElement * ber=NULL;
+
+ if ( e == NULL ) return NULL;
+ if (msg == NULL) {
+ slapi_entry_free(e);
+ return NULL;
+ }
+
+ /*
+ * dn not allocated by slapi
+ * attribute type and values ARE allocated
+ */
+
+ slapi_entry_set_dn( e, ldap_get_dn( ld, msg ) );
+
+ for ( a = ldap_first_attribute( ld, msg, &ber ); a!=NULL;
+ a=ldap_next_attribute( ld, msg, ber ) ) {
+ if(attrsonly) {
+ slapi_entry_add_value(e, a, (Slapi_Value *)NULL);
+ ldap_memfree(a);
+ } else {
+ struct berval ** aVal = ldap_get_values_len( ld, msg, a);
+ slapi_entry_add_values( e, a, aVal);
+
+ ldap_memfree(a);
+ ldap_value_free_len(aVal);
+ }
+ }
+ if ( NULL != ber )
+ ldap_ber_free( ber, 0 );
+
+ return e;
+}
+
+struct berval ** referrals2berval(char ** referrals) {
+
+ int i;
+ struct berval ** val=NULL;
+
+ if (referrals == NULL)
+ return NULL;
+
+ for (i=0;referrals[i];i++) {}
+
+ val = (struct berval **) slapi_ch_calloc(1,(i+1)*sizeof(struct berval *));
+
+ for (i=0;referrals[i];i++) {
+
+ val[i]=(struct berval *) slapi_ch_malloc(sizeof(struct berval));
+ val[i]->bv_len= strlen(referrals[i]);
+ val[i]->bv_val = slapi_ch_strdup(referrals[i]);
+ }
+
+ return val;
+}
+
+char *
+cb_urlparse_err2string( int err )
+{
+ char *s="internal error";
+
+ switch( err ) {
+ case 0:
+ s = "no error";
+ break;
+ case LDAP_URL_ERR_NOTLDAP:
+ s = "missing ldap:// or ldaps://";
+ break;
+ case LDAP_URL_ERR_NODN:
+ s = "missing suffix";
+ break;
+ case LDAP_URL_ERR_BADSCOPE:
+ s = "invalid search scope";
+ break;
+ case LDAP_URL_ERR_MEM:
+ s = "unable to allocate memory";
+ break;
+ case LDAP_URL_ERR_PARAM:
+ s = "bad parameter to an LDAP URL function";
+ break;
+ }
+
+ return( s );
+}
+
+/*
+** Return LDAP_SUCCESS if an internal operation needs to be forwarded to
+** the farm server. We check chaining policy for internal operations
+** We also check max hop count for loop detection for both internal
+** and external operations
+*/
+
+int cb_forward_operation(Slapi_PBlock * pb ) {
+
+ Slapi_Operation *op=NULL;
+ Slapi_Backend *be;
+ struct slapi_componentid *cid = NULL;
+ char *pname;
+ cb_backend_instance *cb;
+ int retcode;
+ LDAPControl **ctrls=NULL;
+
+ slapi_pblock_get (pb, SLAPI_OPERATION, &op);
+
+ /* Loop detection */
+ slapi_pblock_get( pb, SLAPI_REQCONTROLS, &ctrls );
+
+ if ( NULL != ctrls ) {
+ struct berval *ctl_value=NULL;
+ int iscritical=0;
+
+ if (slapi_control_present(ctrls,CB_LDAP_CONTROL_CHAIN_SERVER,&ctl_value,&iscritical)) {
+
+ /* Decode control data */
+ /* hop INTEGER (0 .. maxInt) */
+
+ int hops = 0;
+ int rc;
+ BerElement *ber = NULL;
+
+ if ((ber = ber_init(ctl_value)) == NULL) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "cb_forward_operation: ber_init: Memory allocation failed");
+ return LDAP_NO_MEMORY;
+ }
+ rc = ber_scanf(ber,"i",&hops);
+ if (LBER_ERROR == rc) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "Loop detection control badly encoded.");
+ ber_free(ber,1);
+ return LDAP_LOOP_DETECT;
+ }
+
+ if (hops <=0) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "Max hop count exceeded. Loop detected.\n");
+ ber_free(ber,1);
+ return LDAP_LOOP_DETECT;
+ }
+ ber_free(ber,1);
+ }
+ }
+
+ if ( !operation_is_flag_set(op, OP_FLAG_INTERNAL))
+ return LDAP_SUCCESS;
+
+ slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &cid);
+ if ( cid == NULL ) {
+ /* programming error in the front-end */
+ slapi_log_error(SLAPI_LOG_FATAL, CB_PLUGIN_SUBSYSTEM,
+ "NULL component identity in an internal operation.");
+ return LDAP_UNWILLING_TO_PERFORM;
+ }
+ pname=cid->sci_component_name;
+
+ if (cb_debug_on()) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, CB_PLUGIN_SUBSYSTEM,
+ "internal op received from %s component \n",pname ? pname : "NULL");
+ }
+
+ /* First, make sure chaining is not denied */
+ if (operation_is_flag_set(op, SLAPI_OP_FLAG_NEVER_CHAIN))
+ return LDAP_UNWILLING_TO_PERFORM;
+
+ /* unidentified caller. should not happen */
+ if (pname == NULL)
+ return LDAP_UNWILLING_TO_PERFORM;
+
+ slapi_pblock_get( pb, SLAPI_BACKEND, &be );
+ cb = cb_get_instance(be);
+
+ /* Local policy */
+ PR_RWLock_Rlock(cb->rwl_config_lock);
+ if ( cb->chaining_components != NULL ) {
+ retcode=charray_inlist(cb->chaining_components,pname);
+ PR_RWLock_Unlock(cb->rwl_config_lock);
+ if ( retcode )
+ retcode=LDAP_SUCCESS;
+ else
+ retcode=LDAP_UNWILLING_TO_PERFORM;
+ return retcode;
+ }
+ PR_RWLock_Unlock(cb->rwl_config_lock);
+
+ /* Global policy */
+ PR_RWLock_Rlock(cb->backend_type->config.rwl_config_lock);
+ retcode=charray_inlist(cb->backend_type->config.chaining_components,pname);
+ PR_RWLock_Unlock(cb->backend_type->config.rwl_config_lock);
+
+ if ( retcode )
+ retcode=LDAP_SUCCESS;
+ else
+ retcode=LDAP_UNWILLING_TO_PERFORM;
+ return retcode;
+}
+
+/* better atol -- it understands a trailing multiplier k/m/g
+ * for example, "32k" will be returned as 32768
+ */
+long cb_atol(char *str)
+{
+ long multiplier = 1;
+ char *x = str;
+
+ /* find possible trailing k/m/g */
+ while ((*x >= '0') && (*x <= '9')) x++;
+ switch (*x) {
+ case 'g':
+ case 'G':
+ multiplier *= 1024;
+ case 'm':
+ case 'M':
+ multiplier *= 1024;
+ case 'k':
+ case 'K':
+ multiplier *= 1024;
+ }
+ return (atol(str) * multiplier);
+}
+
+int cb_atoi(char *str)
+{
+ return (int)cb_atol(str);
+}
+
+
+/* This function is used by the instance modify callback to add a new
+ * suffix. It return LDAP_SUCCESS on success.
+ */
+int cb_add_suffix(cb_backend_instance *inst, struct berval **bvals, int apply_mod, char *returntext)
+{
+ Slapi_DN *suffix;
+ int x;
+
+ returntext[0] = '\0';
+ for (x = 0; bvals[x]; x++) {
+ suffix=slapi_sdn_new_dn_byval(bvals[x]->bv_val);
+ if (!slapi_be_issuffix(inst->inst_be, suffix) && apply_mod) {
+ slapi_be_addsuffix(inst->inst_be, suffix);
+ }
+ slapi_sdn_free(&suffix);
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int debug_on=0;
+
+int cb_debug_on()
+{
+ return debug_on;
+}
+
+void cb_set_debug(int on) {
+ debug_on=on;
+}
+
+/* this function is called when state of a backend changes */
+/* The purpose of this function is to handle the associated_be_is_disabled
+ flag in the cb instance structure. The associated database is used to
+ perform local acl evaluations. The associated database can be
+ 1) The chaining backend is the backend of a sub suffix, and the
+ parent suffix has a local backend
+ 2) Entry distribution is being used to distribute write operations to
+ a chaining backend and other operations to a local backend
+ (e.g. a replication hub or consumer)
+ If the associated local backend is being initialized (import), it will be
+ disabled, and it will be impossible to evaluate local acls. In this case,
+ we still want to be able to chain operations to a farm server or another
+ database chain. But the current code will not allow cascading without
+ local acl evaluation (cb_controls.c). associated_be_is_disabled allows
+ us to relax that restriction while the associated backend is disabled
+*/
+/*
+ The first thing we need to do is to determine what our associated backends
+ are. An associated backend is defined as a backend used by the same
+ suffix which uses this cb instance or a backend used by any
+ parent suffix of the suffix which uses this cb instance
+
+ We first see if the be_name is for a local database. If not, then just return.
+ So for the given be_name, we find the suffix which uses it, then the mapping tree
+ entry for that suffix. Then
+ get cb instances used by the suffix and set associated_be_is_disabled
+ get cb instances used by sub suffixes of this suffix and
+ set associated_be_is_disabled
+*/
+void
+cb_be_state_change (void *handle, char *be_name, int old_be_state, int new_be_state)
+{
+ const Slapi_DN *tmpsdn;
+ Slapi_DN *the_be_suffix;
+ char *cookie = NULL;
+ Slapi_Backend *chainbe;
+ Slapi_Backend *the_be = slapi_be_select_by_instance_name(be_name);
+
+ /* no backend? */
+ if (!the_be) {
+ return;
+ }
+
+ /* ignore chaining backends - associated backends must be local */
+ if (slapi_be_is_flag_set(the_be, SLAPI_BE_FLAG_REMOTE_DATA)) {
+ return;
+ }
+
+ /* get the suffix for the local backend */
+ tmpsdn = slapi_be_getsuffix(the_be, 0);
+ if (!tmpsdn) {
+ return;
+ } else {
+ the_be_suffix = slapi_sdn_dup(tmpsdn);
+ }
+
+ /* now, iterate through the chaining backends */
+ for (chainbe = slapi_get_first_backend(&cookie);
+ chainbe; chainbe = slapi_get_next_backend(cookie)) {
+ /* only look at chaining backends */
+ if (slapi_be_is_flag_set(chainbe, SLAPI_BE_FLAG_REMOTE_DATA)) {
+ /* get the suffix */
+ const Slapi_DN *tmpcbsuf = slapi_be_getsuffix(chainbe, 0);
+ if (tmpcbsuf) {
+ /* make a copy - to be safe */
+ Slapi_DN *cbsuffix = slapi_sdn_dup(tmpcbsuf);
+ /* if the suffixes are equal, or the_be_suffix is a suffix
+ of cbsuffix, apply the flag */
+ if (!slapi_sdn_compare(cbsuffix, the_be_suffix) ||
+ slapi_sdn_issuffix(cbsuffix, the_be_suffix)) {
+ cb_backend_instance *cbinst = cb_get_instance(chainbe);
+ if (cbinst) {
+ /* the backend is disabled if the state is not ON */
+ cbinst->associated_be_is_disabled = (new_be_state != SLAPI_BE_STATE_ON);
+ slapi_log_error(SLAPI_LOG_PLUGIN, "chainbe", "cb_be_state_change: set the "
+ "state of chainbe for %s to %d\n",
+ slapi_sdn_get_dn(cbsuffix), (new_be_state != SLAPI_BE_STATE_ON));
+ }
+ }
+ slapi_sdn_free(&cbsuffix);
+ }
+ }
+ }
+
+ /* clean up */
+ slapi_sdn_free(&the_be_suffix);
+ slapi_ch_free_string(&cookie);
+}
diff --git a/ldap/servers/plugins/chainingdb/cbdllmain.c b/ldap/servers/plugins/chainingdb/cbdllmain.c
new file mode 100644
index 00000000..cacf9cb5
--- /dev/null
+++ b/ldap/servers/plugins/chainingdb/cbdllmain.c
@@ -0,0 +1,128 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include "cb.h"
+
+#ifdef _WIN32
+/* Lifted from Q125688
+ * How to Port a 16-bit DLL to a Win32 DLL
+ * on the MSVC 4.0 CD
+ */
+BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved)
+{
+ WSADATA wsadata;
+
+ switch (fdwReason)
+ {
+ case DLL_PROCESS_ATTACH:
+ /* Code from LibMain inserted here. Return TRUE to keep the
+ DLL loaded or return FALSE to fail loading the DLL.
+
+ You may have to modify the code in your original LibMain to
+ account for the fact that it may be called more than once.
+ You will get one DLL_PROCESS_ATTACH for each process that
+ loads the DLL. This is different from LibMain which gets
+ called only once when the DLL is loaded. The only time this
+ is critical is when you are using shared data sections.
+ If you are using shared data sections for statically
+ allocated data, you will need to be careful to initialize it
+ only once. Check your code carefully.
+
+ Certain one-time initializations may now need to be done for
+ each process that attaches. You may also not need code from
+ your original LibMain because the operating system may now
+ be doing it for you.
+ */
+ /*
+ * 16 bit code calls UnlockData()
+ * which is mapped to UnlockSegment in windows.h
+ * in 32 bit world UnlockData is not defined anywhere
+ * UnlockSegment is mapped to GlobalUnfix in winbase.h
+ * and the docs for both UnlockSegment and GlobalUnfix say
+ * ".. function is oboslete. Segments have no meaning
+ * in the 32-bit environment". So we do nothing here.
+ */
+
+ if( errno = WSAStartup(0x0101, &wsadata ) != 0 )
+ return FALSE;
+
+ break;
+
+ case DLL_THREAD_ATTACH:
+ /* Called each time a thread is created in a process that has
+ already loaded (attached to) this DLL. Does not get called
+ for each thread that exists in the process before it loaded
+ the DLL.
+
+ Do thread-specific initialization here.
+ */
+ break;
+
+ case DLL_THREAD_DETACH:
+ /* Same as above, but called when a thread in the process
+ exits.
+
+ Do thread-specific cleanup here.
+ */
+ break;
+
+ case DLL_PROCESS_DETACH:
+ /* Code from _WEP inserted here. This code may (like the
+ LibMain) not be necessary. Check to make certain that the
+ operating system is not doing it for you.
+ */
+ WSACleanup();
+
+ break;
+ }
+ /* The return value is only used for DLL_PROCESS_ATTACH; all other
+ conditions are ignored. */
+ return TRUE; // successful DLL_PROCESS_ATTACH
+}
+#else
+int CALLBACK
+LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine )
+{
+ /*UnlockData( 0 );*/
+ return( 1 );
+}
+#endif
+
+#ifdef LDAP_DEBUG
+#ifndef _WIN32
+#include <stdarg.h>
+#include <stdio.h>
+
+void LDAPDebug( int level, char* fmt, ... )
+{
+ static char debugBuf[1024];
+
+ if (module_ldap_debug && (*module_ldap_debug & level))
+ {
+ va_list ap;
+ va_start (ap, fmt);
+ _snprintf (debugBuf, sizeof(debugBuf), fmt, ap);
+ va_end (ap);
+
+ OutputDebugString (debugBuf);
+ }
+}
+#endif
+#endif
+
+#ifndef _WIN32
+
+/* The 16-bit version of the RTL does not implement perror() */
+
+#include <stdio.h>
+
+void perror( const char *msg )
+{
+ char buf[128];
+ wsprintf( buf, "%s: error %d\n", msg, WSAGetLastError()) ;
+ OutputDebugString( buf );
+}
+
+#endif
diff --git a/ldap/servers/plugins/chainingdb/libcb.def b/ldap/servers/plugins/chainingdb/libcb.def
new file mode 100644
index 00000000..71fe8482
--- /dev/null
+++ b/ldap/servers/plugins/chainingdb/libcb.def
@@ -0,0 +1,15 @@
+; BEGIN COPYRIGHT BLOCK
+; Copyright 2001 Sun Microsystems, Inc.
+; Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+; All rights reserved.
+; END COPYRIGHT BLOCK
+;
+;
+;
+DESCRIPTION 'Netscape Directory Server 7 Chaining Database Plugin'
+;CODE SHARED READ EXECUTE
+;DATA SHARED READ WRITE
+EXPORTS
+ chaining_back_init @1
+ plugin_init_debug_level @2
+ cb_be_state_change @3
diff --git a/ldap/servers/plugins/collation/Makefile b/ldap/servers/plugins/collation/Makefile
new file mode 100644
index 00000000..14619af7
--- /dev/null
+++ b/ldap/servers/plugins/collation/Makefile
@@ -0,0 +1,99 @@
+#
+# BEGIN COPYRIGHT BLOCK
+# Copyright 2001 Sun Microsystems, Inc.
+# Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+# All rights reserved.
+# END COPYRIGHT BLOCK
+#
+LDAP_SRC= ../../..
+MCOM_ROOT= ../../../../..
+
+NOSTDCLEAN=true # don't let nsconfig.mk define target clean
+NOSTDSTRIP=true # don't let nsconfig.mk define target strip
+NSPR20=true # probably should be defined somewhere else (not sure where)
+
+OBJDEST= $(OBJDIR)/lib/liblcoll
+LIBDIR= $(LIB_RELDIR)
+
+include $(MCOM_ROOT)/ldapserver/nsconfig.mk
+include $(LDAP_SRC)/nsldap.mk
+
+INCLUDES+= -I../../slapd -I../../../include
+CFLAGS+=$(SLCFLAGS) -DSLAPD_LOGGING
+
+COLLATION_OBJS= collate.o config.o orfilter.o
+
+ifeq ($(ARCH), WINNT)
+COLLATION_OBJS+= debug.o
+COLLATION_DLL_OBJ=$(addprefix $(OBJDEST)/, dllmain.o)
+DEF_FILE:=./collation.def
+EXTRA_LIBS+= $(NSPRLINK) $(LDAP_SDK_LIBLDAP_DLL) $(LIBSLAPD)
+EXTRA_LIBS_DEP+= $(LIBSLAPD_DEP)
+EXTRA_LIBS_DEP+=$(LDAPSDK_DEP)
+endif
+
+# INCLUDES+= -I. -I$(ACLINC) -I$(MCOM_ROOT)/ldapserver/lib
+
+# ICU stuff
+INCLUDES+= $(ICU_INCLUDE)
+EXTRA_LIBS+=$(ICULINK)
+
+ifeq ($(ARCH), HPUX)
+EXTRA_LIBS_DEP += $(LDAPSDK_DEP) $(NSPR_DEP) $(SECURITY_DEP)
+EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(SECURITYLINK) $(NSPRLINK) $(ICULINK)
+endif
+
+ifeq ($(ARCH), AIX)
+EXTRA_LIBS+= $(LIBSLAPDLINK) $(NSPRLINK) $(LDAPLINK)
+EXTRA_LIBS_DEP+= $(LIBSLAPD_DEP)
+EXTRA_LIBS_DEP+=$(LDAPSDK_DEP)
+LD=ld
+endif
+
+OBJS= $(addprefix $(OBJDEST)/, $(COLLATION_OBJS))
+COLLATION= $(addprefix $(LIBDIR)/, $(COLLATION_DLL).$(DLL_SUFFIX))
+
+clientSDK:
+
+all: $(OBJDEST) $(LIBDIR) $(COLLATION)
+ifeq (0, 1)
+# Where the heck did the compiler options come from?
+ @echo ARCH=$(ARCH)
+ @echo DEBUG=$(DEBUG)
+ @echo BUILD_OPT=$(BUILD_OPT)
+ @echo CFLAGS=$(CFLAGS)
+ @echo " MCC_DEBUG="$(MCC_DEBUG)
+ @echo " PLATFORMCFLAGS="$(PLATFORMCFLAGS)
+ @echo " ACFLAGS="$(ACFLAGS)
+ @echo " EXTRACFLAGS="$(EXTRACFLAGS)
+ @echo " UNPROTOCFLAGS="$(UNPROTOCFLAGS)
+ @echo " SLCFLAGS="$(SLCFLAGS)
+ @echo "ALDFLAGS="$(ALDFLAGS)
+ @echo "DLL_LDFLAGS="$(DLL_LDFLAGS)
+ @echo "DLL_EXPORT_FLAGS="$(DLL_EXPORT_FLAGS)
+endif
+
+ifeq ($(ARCH), WINNT)
+$(COLLATION): $(OBJS) $(COLLATION_DLL_OBJ) $(EXTRA_LIBS_DEP) $(DEF_FILE)
+ $(LINK_DLL) $(COLLATION_DLL_OBJ) $(EXTRA_LIBS) /DEF:$(DEF_FILE)
+else
+ifeq ($(ARCH), AIX)
+$(COLLATION): $(OBJS) $(COLLATION_DLL_OBJ) $(EXTRA_LIBS_DEP)
+ $(LINK_DLL) $(COLLATION_DLL_OBJ) $(EXTRA_LIBS)
+else
+$(COLLATION): $(OBJS) $(EXTRA_LIBS_DEP)
+ $(LINK_DLL) $(EXTRA_LIBS)
+endif
+endif
+
+veryclean: clean
+
+clean:
+ $(RM) $(OBJS)
+ifeq ($(ARCH), WINNT)
+ $(RM) $(COLLATION_DLL_OBJ)
+endif
+ $(RM) $(COLLATION)
+
+$(OBJDEST):
+ $(MKDIR) $(OBJDEST)
diff --git a/ldap/servers/plugins/collation/collate.c b/ldap/servers/plugins/collation/collate.c
new file mode 100644
index 00000000..603caf53
--- /dev/null
+++ b/ldap/servers/plugins/collation/collate.c
@@ -0,0 +1,454 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* collate.c - implementation of indexing, using a Collation */
+
+#include "collate.h"
+#include <string.h> /* memcpy */
+
+#include <unicode/ucol.h> /* Collation */
+#include <unicode/ucnv.h> /* Conversion */
+#include <unicode/ustring.h> /* UTF8 conversion */
+
+#include <ldap.h> /* LDAP_UTF8LEN */
+#include <slap.h> /* for strcasecmp on non-UNIX platforms and correct debug macro */
+
+void
+collation_init( char *configpath )
+ /* Called once per process, to initialize globals. */
+{
+ /* ICU needs no initialization? */
+}
+
+typedef struct coll_profile_t { /* Collator characteristics */
+ const char* language;
+ const char* country;
+ const char* variant;
+ UColAttributeValue strength; /* one of UCOL_PRIMARY = 0, UCOL_SECONDARY = 1, UCOL_TERTIARY = 2, UCOL_QUATERNARY = 3, UCOL_IDENTICAL = 4 */
+ UColAttributeValue decomposition; /* one of UCOL_OFF = 0, UCOL_DEFAULT = 1, UCOL_ON = 2 */
+} coll_profile_t;
+
+typedef struct coll_id_t { /* associates an OID with a coll_profile_t */
+ char* oid;
+ coll_profile_t* profile;
+} coll_id_t;
+
+/* A list of all OIDs that identify collator profiles: */
+static const coll_id_t** collation_id = NULL;
+static size_t collation_ids = 0;
+
+int
+collation_config (size_t cargc, char** cargv,
+ const char* fname, size_t lineno)
+ /* Process one line from a configuration file.
+ Return 0 if it's OK, -1 if it's not recognized.
+ Any other return value is a process exit code.
+ */
+{
+ if (cargc <= 0) { /* Bizarre. Oh, well... */
+ } else if (!strcasecmp (cargv[0], "NLS")) {
+ /* ignore - not needed anymore with ICU - was used to get path for NLS_Initialize */
+ } else if (!strcasecmp (cargv[0], "collation")) {
+ if ( cargc < 7 ) {
+ LDAPDebug (LDAP_DEBUG_ANY,
+ "%s: line %lu ignored: only %lu arguments (expected "
+ "collation language country variant strength decomposition oid ...)\n",
+ fname, (unsigned long)lineno, (unsigned long)cargc );
+ } else {
+ auto size_t arg;
+ auto coll_profile_t* profile = (coll_profile_t*) slapi_ch_calloc (1, sizeof (coll_profile_t));
+ if (*cargv[1]) profile->language = slapi_ch_strdup (cargv[1]);
+ if (*cargv[2]) profile->country = slapi_ch_strdup (cargv[2]);
+ if (*cargv[3]) profile->variant = slapi_ch_strdup (cargv[3]);
+ switch (atoi(cargv[4])) {
+ case 1: profile->strength = UCOL_PRIMARY; break;
+ case 2: profile->strength = UCOL_SECONDARY; /* no break here? fall through? wtf? */
+ case 3: profile->strength = UCOL_TERTIARY; break;
+ case 4: profile->strength = UCOL_IDENTICAL; break;
+ default: profile->strength = UCOL_SECONDARY;
+ LDAPDebug (LDAP_DEBUG_ANY,
+ "%s: line %lu: strength \"%s\" not supported (will use 2)\n",
+ fname, (unsigned long)lineno, cargv[4]);
+ break;
+ }
+ switch (atoi(cargv[5])) {
+ case 1: profile->decomposition = UCOL_OFF; break;
+ case 2: profile->decomposition = UCOL_DEFAULT; /* no break here? fall through? wtf? */
+ case 3: profile->decomposition = UCOL_ON; break;
+ default: profile->decomposition = UCOL_DEFAULT;
+ LDAPDebug (LDAP_DEBUG_ANY,
+ "%s: line %lu: decomposition \"%s\" not supported (will use 2)\n",
+ fname, (unsigned long)lineno, cargv[5]);
+ break;
+ }
+
+ {
+ char descStr[256];
+ char nameOrder[256];
+ char nameSubstring[256];
+ char oidString[256];
+ char *tmpStr=NULL;
+ Slapi_MatchingRuleEntry *mrentry=slapi_matchingrule_new();
+
+ if(UCOL_PRIMARY == profile->strength) {
+ strcpy(nameOrder,"caseIgnoreOrderingMatch");
+ strcpy(nameSubstring,"caseIgnoreSubstringMatch");
+ }
+ else {
+ strcpy(nameOrder,"caseExactOrderingMatch");
+ strcpy(nameSubstring,"caseExactSubstringMatch");
+ }
+
+ if(cargc > 7) {
+ strcat(nameOrder,"-");
+ strcat(nameOrder,cargv[7]);
+ strcat(nameSubstring,"-");
+ strcat(nameSubstring,cargv[7]);
+ slapi_matchingrule_set(mrentry,SLAPI_MATCHINGRULE_NAME,
+ (void *)slapi_ch_strdup(nameOrder));
+ }
+ else {
+ if(0 != cargv[1][0]) {
+ strcat(nameOrder,"-");
+ strcat(nameSubstring,"-");
+ }
+ strcat(nameOrder,cargv[1]);
+ strcat(nameSubstring,cargv[1]);
+ slapi_matchingrule_set(mrentry,SLAPI_MATCHINGRULE_NAME,
+ (void *)slapi_ch_strdup(nameOrder));
+ }
+ strcpy(oidString,cargv[6]);
+ slapi_matchingrule_set(mrentry,SLAPI_MATCHINGRULE_OID,
+ (void *)slapi_ch_strdup(oidString));
+ if(0 != cargv[2][0]) {
+ sprintf(descStr,"%s-%s",cargv[1],cargv[2]);
+ }
+ else {
+ strcpy(descStr,cargv[1]);
+ }
+ slapi_matchingrule_set(mrentry,SLAPI_MATCHINGRULE_DESC,
+ (void *)slapi_ch_strdup(descStr));
+ slapi_matchingrule_set(mrentry,SLAPI_MATCHINGRULE_SYNTAX,
+ (void *)slapi_ch_strdup(DIRSTRING_SYNTAX_OID));
+ slapi_matchingrule_register(mrentry);
+ slapi_matchingrule_get(mrentry,SLAPI_MATCHINGRULE_NAME,
+ (void *)&tmpStr);
+ slapi_ch_free((void **)&tmpStr);
+ slapi_matchingrule_get(mrentry,SLAPI_MATCHINGRULE_OID,
+ (void *)&tmpStr);
+ slapi_ch_free((void **)&tmpStr);
+ slapi_matchingrule_set(mrentry,SLAPI_MATCHINGRULE_NAME,
+ (void *)slapi_ch_strdup(nameSubstring));
+ strcat(oidString,".6");
+ slapi_matchingrule_set(mrentry,SLAPI_MATCHINGRULE_OID,
+ (void *)slapi_ch_strdup(oidString));
+ slapi_matchingrule_register(mrentry);
+ slapi_matchingrule_free(&mrentry,1);
+ }
+
+
+ for (arg = 6; arg < cargc; ++arg) {
+ auto coll_id_t* id = (coll_id_t*) slapi_ch_malloc (sizeof (coll_id_t));
+ id->oid = slapi_ch_strdup (cargv[arg]);
+ id->profile = profile;
+ if (collation_ids <= 0) {
+ collation_id = (const coll_id_t**) slapi_ch_malloc (2 * sizeof (coll_id_t*));
+ } else {
+ collation_id = (const coll_id_t**) slapi_ch_realloc
+ ((void*)collation_id, (collation_ids + 2) * sizeof (coll_id_t*));
+ }
+ collation_id [collation_ids++] = id;
+ collation_id [collation_ids] = NULL;
+ }
+ }
+ } else {
+ return -1; /* unrecognized */
+ }
+ return 0; /* success */
+}
+
+typedef struct collation_indexer_t
+ /* A kind of indexer, implemented using an ICU Collator */
+{
+ UCollator* collator;
+ UConverter* converter;
+ struct berval** ix_keys;
+ int is_default_collator;
+} collation_indexer_t;
+
+/*
+ Caller must ensure that U == NULL and Ulen == 0 the first time called
+*/
+static UErrorCode
+SetUnicodeStringFromUTF_8 (UChar** U, int32_t* Ulen, int *isAlloced, const struct berval* bv)
+ /* Copy the UTF-8 string bv into the UnicodeString U,
+ but remove leading and trailing whitespace, and
+ convert consecutive whitespaces into a single space.
+ Ulen is set to the number of UChars in the array (not necessarily the number of bytes!)
+ */
+{
+ size_t n;
+ int32_t len = 0; /* length of non-space string */
+ int32_t needLen = 0; /* number of bytes needed for string */
+ UErrorCode err = U_ZERO_ERROR;
+ const char* s = bv->bv_val;
+ const char* begin = NULL; /* will point to beginning of non-space in val */
+ const char* end = NULL; /* will point to the first space after the last non-space char in val */
+ int32_t nUchars = 0;
+
+ if (!bv->bv_len) { /* no value? */
+ return U_INVALID_FORMAT_ERROR; /* don't know what else to use here */
+ }
+
+ /* first, set s to the first non-space char in bv->bv_val */
+ for (n = 0; (n < bv->bv_len) && ldap_utf8isspace((char *)s); ) { /* cast away const */
+ const char *next = LDAP_UTF8NEXT((char *)s); /* cast away const */
+ n += (next - s); /* count bytes, not chars */
+ s = next;
+ }
+ begin = s; /* begin points to first non-space char in val */
+
+ if (n >= bv->bv_len) { /* value is all spaces? */
+ return U_INVALID_FORMAT_ERROR; /* don't know what else to use here */
+ }
+
+ s = bv->bv_val + (bv->bv_len-1); /* move s to last char of bv_val */
+ end = s; /* end points at last char of bv_val - may change below */
+ /* find the last non-null and non-space char of val */
+ for (n = bv->bv_len; (n > 0) && (!*s || ldap_utf8isspace((char *)s));) {
+ const char *prev = LDAP_UTF8PREV((char *)s);
+ end = prev;
+ n -= (s - prev); /* count bytes, not chars */
+ s = prev;
+ }
+
+ /* end now points at last non-null/non-space of val */
+ if (n < 0) { /* bogus */
+ return U_INVALID_FORMAT_ERROR; /* don't know what else to use here */
+ }
+
+ len = LDAP_UTF8NEXT((char *)end) - begin;
+
+ u_strFromUTF8(*U, *Ulen, &nUchars, begin, len, &err);
+ if (nUchars > *Ulen) { /* need more space */
+ if (*isAlloced) { /* realloc space */
+ *U = (UChar *)slapi_ch_realloc((char *)*U, sizeof(UChar) * nUchars);
+ } else { /* must use malloc */
+ *U = (UChar *)slapi_ch_malloc(sizeof(UChar) * nUchars);
+ *isAlloced = 1; /* no longer using fixed buffer */
+ }
+ *Ulen = nUchars;
+ err = U_ZERO_ERROR; /* reset */
+ u_strFromUTF8(*U, *Ulen, NULL, begin, len, &err);
+ } else {
+ *Ulen = nUchars;
+ }
+
+ return err;
+}
+
+static struct berval**
+collation_index (indexer_t* ix, struct berval** bvec, struct berval** prefixes)
+{
+ collation_indexer_t* etc = (collation_indexer_t*) ix->ix_etc;
+ struct berval** keys = NULL;
+ if (bvec) {
+ char keyBuffer[128]; /* try to use static space buffer to avoid malloc */
+ int32_t keyLen = sizeof(keyBuffer);
+ char* key = keyBuffer; /* but key can grow if necessary */
+ size_t keyn = 0;
+ struct berval** bv;
+ UChar charBuffer[128]; /* try to use static space buffer */
+ int32_t nChars = sizeof(charBuffer)/sizeof(UChar); /* but grow if necessary */
+ UChar *chars = charBuffer; /* try to reuse this */
+ int isAlloced = 0; /* using fixed buffer */
+
+ for (bv = bvec; *bv; ++bv) {
+ /* if chars is allocated, nChars will be the capacity and the number of chars in chars */
+ /* otherwise, nChars will be the number of chars, which may be less than the capacity */
+ if (!isAlloced) {
+ nChars = sizeof(charBuffer)/sizeof(UChar); /* reset */
+ }
+ if (U_ZERO_ERROR == SetUnicodeStringFromUTF_8 (&chars, &nChars, &isAlloced, *bv)) {
+ /* nChars is now the number of UChar in chars, which may be less than the
+ capacity of charBuffer if not allocated */
+ struct berval* prefix = prefixes ? prefixes[bv-bvec] : NULL;
+ const size_t prefixLen = prefix ? prefix->bv_len : 0;
+ struct berval* bk = NULL;
+ int32_t realLen; /* real length of key, not keyLen which is buffer size */
+
+ /* try to get the sort key using key and keyLen; only grow key
+ if we need to */
+ /* can use -1 for char len since the conversion from UTF8
+ null terminates the string */
+ realLen = ucol_getSortKey(etc->collator, chars, nChars, (uint8_t *)key, keyLen);
+ if (realLen > keyLen) { /* need more space */
+ if (key == keyBuffer) {
+ key = (char*)slapi_ch_malloc(sizeof(char) * realLen);
+ } else {
+ key = (char*)slapi_ch_realloc(key, sizeof(char) * realLen);
+ }
+ keyLen = ucol_getSortKey(etc->collator, chars, nChars, (uint8_t *)key, realLen);
+ }
+ if (realLen > 0) {
+ bk = (struct berval*) slapi_ch_malloc (sizeof(struct berval));
+
+ bk->bv_len = prefixLen + realLen;
+ bk->bv_val = slapi_ch_malloc (bk->bv_len + 1);
+ if (prefixLen) {
+ memcpy(bk->bv_val, prefix->bv_val, prefixLen);
+ }
+ memcpy(bk->bv_val + prefixLen, key, realLen);
+ bk->bv_val[bk->bv_len] = '\0';
+ LDAPDebug (LDAP_DEBUG_FILTER, "collation_index(%.*s) %lu bytes\n",
+ bk->bv_len, bk->bv_val, (unsigned long)bk->bv_len);
+ keys = (struct berval**)
+ slapi_ch_realloc ((void*)keys, sizeof(struct berval*) * (keyn + 2));
+ keys[keyn++] = bk;
+ keys[keyn] = NULL;
+ }
+ }
+ }
+ if (chars != charBuffer) { /* realloc'ed, need to free */
+ slapi_ch_free((void **)&chars);
+ }
+ if (key != keyBuffer) { /* realloc'ed, need to free */
+ slapi_ch_free_string(&key);
+ }
+ }
+ if (etc->ix_keys != NULL) ber_bvecfree (etc->ix_keys);
+ etc->ix_keys = keys;
+ return keys;
+}
+
+static void
+collation_indexer_destroy (indexer_t* ix)
+ /* The destructor function for a collation-based indexer. */
+{
+ collation_indexer_t* etc = (collation_indexer_t*) ix->ix_etc;
+ if (etc->converter) {
+ ucnv_close(etc->converter);
+ etc->converter = NULL;
+ }
+ if (!etc->is_default_collator) {
+ /* Don't delete the default collation - it seems to cause problems */
+ ucol_close(etc->collator);
+ etc->collator = NULL;
+ }
+ if (etc->ix_keys != NULL) {
+ ber_bvecfree (etc->ix_keys);
+ etc->ix_keys = NULL;
+ }
+ slapi_ch_free((void**)&ix->ix_etc);
+ ix->ix_etc = NULL; /* just for hygiene */
+}
+
+static UErrorCode
+s_newNamedLocaleFromComponents(char **locale, const char *lang, const char *country, const char *variant)
+{
+ UErrorCode err = U_ZERO_ERROR;
+ int hasLang = (lang && *lang);
+ int hasC = (country && *country);
+ int hasVar = (variant && *variant);
+
+ *locale = NULL;
+ if (hasLang) {
+ *locale = PR_smprintf("%s%s%s%s%s", lang, (hasC ? "_" : ""), (hasC ? country : ""),
+ (hasVar ? "_" : ""), (hasVar ? variant : ""));
+ } else {
+ err = U_INVALID_FORMAT_ERROR; /* don't know what else to use here */
+ }
+
+ return err;
+}
+
+indexer_t*
+collation_indexer_create (const char* oid)
+ /* Return a new indexer, based on the collation identified by oid.
+ Return NULL if this can't be done.
+ */
+{
+ indexer_t* ix = NULL;
+ const coll_id_t** id = collation_id;
+ char* locale = NULL; /* NULL == default locale */
+ if (id) for (; *id; ++id) {
+ if (!strcasecmp (oid, (*id)->oid)) {
+ const coll_profile_t* profile = (*id)->profile;
+ const int is_default = (profile->language == NULL &&
+ profile->country == NULL &&
+ profile->variant == NULL);
+ UErrorCode err = U_ZERO_ERROR;
+ if ( ! is_default) {
+ if (locale) {
+ PR_smprintf_free(locale);
+ locale = NULL;
+ }
+ err = s_newNamedLocaleFromComponents(&locale,
+ profile->language,
+ profile->country,
+ profile->variant);
+ }
+ if (err == U_ZERO_ERROR) {
+ UCollator* coll = ucol_open(locale, &err);
+ /*
+ * If we found exactly the right collator for this locale,
+ * or if we found a fallback one, or if we are happy with
+ * the default, use it.
+ */
+ if (err == U_ZERO_ERROR || err == U_USING_FALLBACK_WARNING ||
+ (err == U_USING_DEFAULT_WARNING && is_default)) {
+ collation_indexer_t* etc = (collation_indexer_t*)
+ slapi_ch_calloc (1, sizeof (collation_indexer_t));
+ ix = (indexer_t*) slapi_ch_calloc (1, sizeof (indexer_t));
+ ucol_setAttribute (coll, UCOL_STRENGTH, profile->strength, &err);
+ if (err != U_ZERO_ERROR) {
+ LDAPDebug (LDAP_DEBUG_ANY, "collation_indexer_create: could not "
+ "set the collator strength for oid %s to %d: err %d\n",
+ oid, profile->strength, err);
+ }
+ ucol_setAttribute (coll, UCOL_DECOMPOSITION_MODE, profile->decomposition, &err);
+ if (err != U_ZERO_ERROR) {
+ LDAPDebug (LDAP_DEBUG_ANY, "collation_indexer_create: could not "
+ "set the collator decomposition mode for oid %s to %d: err %d\n",
+ oid, profile->decomposition, err);
+ }
+ etc->collator = coll;
+ etc->is_default_collator = is_default;
+ for (id = collation_id; *id; ++id) {
+ if ((*id)->profile == profile) {
+ break; /* found the 'official' id */
+ }
+ }
+ ix->ix_etc = etc;
+ ix->ix_oid = (*id)->oid;
+ ix->ix_index = collation_index;
+ ix->ix_destroy = collation_indexer_destroy;
+ break; /* return */
+ /* free (etc); */
+ /* free (ix); */
+ } else if (err == U_USING_DEFAULT_WARNING) {
+ LDAPDebug (LDAP_DEBUG_FILTER, "collation_indexer_create: could not "
+ "create an indexer for OID %s for locale %s and could not "
+ "use default locale\n",
+ oid, (locale ? locale : "(default)"), NULL);
+ } else { /* error */
+ LDAPDebug (LDAP_DEBUG_FILTER, "collation_indexer_create: could not "
+ "create an indexer for OID %s for locale %s: err = %d\n",
+ oid, (locale ? locale : "(default)"), err);
+ }
+ if (coll) {
+ ucol_close (coll);
+ coll = NULL;
+ }
+ }
+ break; /* failed to create the specified collator */
+ }
+ }
+ if (locale) {
+ PR_smprintf_free(locale);
+ locale = NULL;
+ }
+ return ix;
+}
diff --git a/ldap/servers/plugins/collation/collate.h b/ldap/servers/plugins/collation/collate.h
new file mode 100644
index 00000000..29e51022
--- /dev/null
+++ b/ldap/servers/plugins/collation/collate.h
@@ -0,0 +1,36 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#ifndef _COLLATE_H_
+#define _COLLATE_H_
+
+#include <stddef.h> /* size_t */
+#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */
+
+struct indexer_t;
+
+typedef void (*ix_destroy_t) (struct indexer_t*);
+typedef struct berval** (*ix_index_t) (struct indexer_t*, struct berval** values,
+ struct berval** prefixes /* inserted into each key */);
+
+typedef struct indexer_t
+{
+ char* ix_oid;
+ ix_index_t ix_index; /* map values to index keys */
+ ix_destroy_t ix_destroy;
+ void* ix_etc; /* whatever state the implementation needs */
+} indexer_t;
+
+extern void
+collation_init( char *configpath );
+
+extern int
+collation_config (size_t argc, char** argv, const char* fname, size_t lineno);
+
+extern indexer_t*
+collation_indexer_create (const char* oid);
+
+#endif
diff --git a/ldap/servers/plugins/collation/collation.def b/ldap/servers/plugins/collation/collation.def
new file mode 100644
index 00000000..bd5d531b
--- /dev/null
+++ b/ldap/servers/plugins/collation/collation.def
@@ -0,0 +1,10 @@
+; BEGIN COPYRIGHT BLOCK
+; Copyright 2001 Sun Microsystems, Inc.
+; Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+; All rights reserved.
+; END COPYRIGHT BLOCK
+;
+DESCRIPTION 'Netscape Directory Server 7 Collation Plugin'
+EXPORTS
+ orderingRule_init @2
+ plugin_init_debug_level @3
diff --git a/ldap/servers/plugins/collation/config.c b/ldap/servers/plugins/collation/config.c
new file mode 100644
index 00000000..ef3e66cf
--- /dev/null
+++ b/ldap/servers/plugins/collation/config.c
@@ -0,0 +1,178 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include "collate.h"
+#include "config.h"
+#include <stdio.h>
+#include <string.h>
+#include "slap.h"
+
+#define MAXARGS 16
+
+static char *
+strtok_quote( char *line, char *sep )
+{
+ int inquote;
+ char *tmp, *d;
+ static char *next;
+
+ if ( line != NULL ) {
+ next = line;
+ }
+ while ( *next && strchr( sep, *next ) ) {
+ next++;
+ }
+
+ if ( *next == '\0' ) {
+ next = NULL;
+ return( NULL );
+ }
+
+ d = tmp = next;
+ for ( inquote = 0; *next; next++ ) {
+ switch ( *next ) {
+ case '"':
+ if ( inquote ) {
+ inquote = 0;
+ } else {
+ inquote = 1;
+ }
+ break;
+
+#ifndef _WIN32
+ case '\\':
+ *d++ = *++next;
+ break;
+#endif
+
+ default:
+ if ( ! inquote ) {
+ if ( strchr( sep, *next ) != NULL ) {
+ *d++ = '\0';
+ next++;
+ return( tmp );
+ }
+ }
+ *d++ = *next;
+ break;
+ }
+ }
+ *d = '\0';
+
+ return( tmp );
+}
+
+static void
+fp_parse_line(
+ char *line,
+ int *argcp,
+ char **argv
+)
+{
+ char * token;
+
+ *argcp = 0;
+ for ( token = strtok_quote( line, " \t" ); token != NULL;
+ token = strtok_quote( NULL, " \t" ) ) {
+ if ( *argcp == MAXARGS ) {
+ LDAPDebug( LDAP_DEBUG_ANY, "Too many tokens (max %d)\n",
+ MAXARGS, 0, 0 );
+ exit( 1 );
+ }
+ argv[(*argcp)++] = token;
+ }
+ argv[*argcp] = NULL;
+}
+
+static char buf[BUFSIZ];
+static char *line;
+static int lmax, lcur;
+
+static void
+fp_getline_init( int *lineno )
+{
+ *lineno = -1;
+ buf[0] = '\0';
+}
+
+#define CATLINE( buf ) { \
+ int len; \
+ len = strlen( buf ); \
+ while ( lcur + len + 1 > lmax ) { \
+ lmax += BUFSIZ; \
+ line = (char *) slapi_ch_realloc( line, lmax ); \
+ } \
+ strcpy( line + lcur, buf ); \
+ lcur += len; \
+}
+
+static char *
+fp_getline( FILE *fp, int *lineno )
+{
+ char *p;
+
+ lcur = 0;
+ CATLINE( buf );
+ (*lineno)++;
+
+ /* hack attack - keeps us from having to keep a stack of bufs... */
+ if ( strncasecmp( line, "include", 7 ) == 0 ) {
+ buf[0] = '\0';
+ return( line );
+ }
+
+ while ( fgets( buf, sizeof(buf), fp ) != NULL ) {
+ if ( (p = strchr( buf, '\n' )) != NULL ) {
+ *p = '\0';
+ }
+ if ( ! isspace( buf[0] ) ) {
+ return( line );
+ }
+
+ CATLINE( buf );
+ (*lineno)++;
+ }
+ buf[0] = '\0';
+
+ return( line[0] ? line : NULL );
+}
+
+void
+collation_read_config( char *fname )
+{
+ FILE* fp;
+ char* line;
+ int cargc;
+ char* cargv[MAXARGS];
+ int lineno;
+
+ fp = fopen( fname, "r" );
+ if ( fp == NULL ) {
+ LDAPDebug( LDAP_DEBUG_ANY,
+ "could not open config file \"%s\" - absolute path?\n",
+ fname, 0, 0 );
+ return; /* Do not exit */
+ }
+
+ LDAPDebug( LDAP_DEBUG_CONFIG, "reading config file %s\n", fname, 0, 0 );
+
+ fp_getline_init( &lineno );
+ while ( (line = fp_getline( fp, &lineno )) != NULL ) {
+ /* skip comments and blank lines */
+ if ( line[0] == '#' || line[0] == '\0' ) {
+ continue;
+ }
+ LDAPDebug( LDAP_DEBUG_CONFIG, "line %d: %s\n", lineno, line, 0 );
+ fp_parse_line( line, &cargc, cargv );
+ if ( cargc < 1 ) {
+ LDAPDebug( LDAP_DEBUG_ANY,
+ "%s: line %d: bad config line (ignored)\n",
+ fname, lineno, 0 );
+ continue;
+ }
+ collation_config (cargc, cargv, fname, lineno);
+ }
+ fclose(fp);
+}
diff --git a/ldap/servers/plugins/collation/config.h b/ldap/servers/plugins/collation/config.h
new file mode 100644
index 00000000..1a4aef66
--- /dev/null
+++ b/ldap/servers/plugins/collation/config.h
@@ -0,0 +1,12 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#ifndef _COLL_CONFIG_H_
+#define _COLL_CONFIG_H_
+
+extern void
+collation_read_config( char *fname );
+
+#endif
diff --git a/ldap/servers/plugins/collation/debug.c b/ldap/servers/plugins/collation/debug.c
new file mode 100644
index 00000000..99266a33
--- /dev/null
+++ b/ldap/servers/plugins/collation/debug.c
@@ -0,0 +1,14 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#ifdef _WIN32
+int *module_ldap_debug = 0;
+
+void
+plugin_init_debug_level (int *level_ptr)
+{
+ module_ldap_debug = level_ptr;
+}
+#endif
diff --git a/ldap/servers/plugins/collation/dllmain.c b/ldap/servers/plugins/collation/dllmain.c
new file mode 100644
index 00000000..fa158500
--- /dev/null
+++ b/ldap/servers/plugins/collation/dllmain.c
@@ -0,0 +1,129 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/*
+ * Microsoft Windows specifics for collation DLL
+ */
+#include "ldap.h"
+
+#ifdef _WIN32
+/* Lifted from Q125688
+ * How to Port a 16-bit DLL to a Win32 DLL
+ * on the MSVC 4.0 CD
+ */
+BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved)
+{
+ WSADATA wsadata;
+
+ switch (fdwReason)
+ {
+ case DLL_PROCESS_ATTACH:
+ /* Code from LibMain inserted here. Return TRUE to keep the
+ DLL loaded or return FALSE to fail loading the DLL.
+
+ You may have to modify the code in your original LibMain to
+ account for the fact that it may be called more than once.
+ You will get one DLL_PROCESS_ATTACH for each process that
+ loads the DLL. This is different from LibMain which gets
+ called only once when the DLL is loaded. The only time this
+ is critical is when you are using shared data sections.
+ If you are using shared data sections for statically
+ allocated data, you will need to be careful to initialize it
+ only once. Check your code carefully.
+
+ Certain one-time initializations may now need to be done for
+ each process that attaches. You may also not need code from
+ your original LibMain because the operating system may now
+ be doing it for you.
+ */
+ /*
+ * 16 bit code calls UnlockData()
+ * which is mapped to UnlockSegment in windows.h
+ * in 32 bit world UnlockData is not defined anywhere
+ * UnlockSegment is mapped to GlobalUnfix in winbase.h
+ * and the docs for both UnlockSegment and GlobalUnfix say
+ * ".. function is oboslete. Segments have no meaning
+ * in the 32-bit environment". So we do nothing here.
+ */
+
+ if( errno = WSAStartup(0x0101, &wsadata ) != 0 )
+ return FALSE;
+
+ break;
+
+ case DLL_THREAD_ATTACH:
+ /* Called each time a thread is created in a process that has
+ already loaded (attached to) this DLL. Does not get called
+ for each thread that exists in the process before it loaded
+ the DLL.
+
+ Do thread-specific initialization here.
+ */
+ break;
+
+ case DLL_THREAD_DETACH:
+ /* Same as above, but called when a thread in the process
+ exits.
+
+ Do thread-specific cleanup here.
+ */
+ break;
+
+ case DLL_PROCESS_DETACH:
+ /* Code from _WEP inserted here. This code may (like the
+ LibMain) not be necessary. Check to make certain that the
+ operating system is not doing it for you.
+ */
+ WSACleanup();
+
+ break;
+ }
+ /* The return value is only used for DLL_PROCESS_ATTACH; all other
+ conditions are ignored. */
+ return TRUE; // successful DLL_PROCESS_ATTACH
+}
+#else
+int CALLBACK
+LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine )
+{
+ /*UnlockData( 0 );*/
+ return( 1 );
+}
+#endif
+
+#ifdef LDAP_DEBUG
+#ifndef _WIN32
+#include <stdarg.h>
+#include <stdio.h>
+
+void LDAPDebug( int level, char* fmt, ... )
+{
+ static char debugBuf[1024];
+
+ if (module_ldap_debug && (*module_ldap_debug & level))
+ {
+ va_list ap;
+ va_start (ap, fmt);
+ _snprintf (debugBuf, sizeof(debugBuf), fmt, ap);
+ va_end (ap);
+
+ OutputDebugString (debugBuf);
+ }
+}
+#endif
+#endif
+
+#ifndef _WIN32
+/* The 16-bit version of the RTL does not implement perror() */
+#include <stdio.h>
+
+void perror( const char *msg )
+{
+ char buf[128];
+ wsprintf( buf, "%s: error %d\n", msg, WSAGetLastError()) ;
+ OutputDebugString( buf );
+}
+
+#endif
diff --git a/ldap/servers/plugins/collation/orfilter.c b/ldap/servers/plugins/collation/orfilter.c
new file mode 100644
index 00000000..65222baa
--- /dev/null
+++ b/ldap/servers/plugins/collation/orfilter.c
@@ -0,0 +1,984 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* orfilter.c - implementation of ordering rule filter */
+
+#include <ldap.h> /* LDAP_UTF8INC */
+#include <slap.h> /* for debug macros */
+#include <slapi-plugin.h> /* slapi_berval_cmp, SLAPI_BERVAL_EQ */
+#include "collate.h" /* indexer_t, collation_xxx */
+#include "config.h" /* collation_read_config */
+#include "orfilter.h"
+
+#ifdef HPUX11
+#include <dl.h>
+#endif /* HPUX11 */
+
+static indexer_t*
+indexer_create (const char* oid)
+{
+ return collation_indexer_create (oid);
+}
+
+static void
+indexer_free (indexer_t* ix)
+{
+ if (ix->ix_destroy != NULL) {
+ ix->ix_destroy (ix);
+ }
+ slapi_ch_free((void**)&ix);
+}
+
+typedef struct or_filter_t {
+ /* implements a filter, using an indexer */
+ char* or_type;
+ int or_op; /* LDAPI_OP_xxx */
+ char* or_oid;
+ struct berval** or_values;
+ struct berval** or_match_keys;
+ struct berval** or_index_keys;
+ indexer_t* or_indexer; /* used to construct or_match_keys and or_index_keys */
+} or_filter_t;
+
+static or_filter_t*
+or_filter_get (Slapi_PBlock* pb)
+{
+ auto void* obj = NULL;
+ if ( ! slapi_pblock_get (pb, SLAPI_PLUGIN_OBJECT, &obj)) {
+ return (or_filter_t*)obj;
+ }
+ return NULL;
+}
+
+static int
+or_filter_destroy (Slapi_PBlock* pb)
+{
+ auto or_filter_t* or = or_filter_get (pb);
+ LDAPDebug (LDAP_DEBUG_FILTER, "or_filter_destroy(%p)\n", (void*)or, 0, 0);
+ if (or != NULL) {
+ slapi_ch_free((void**)&or->or_type);
+ slapi_ch_free((void**)&or->or_oid);
+ if (or->or_values != NULL) {
+ ber_bvecfree (or->or_values);
+ or->or_values = NULL;
+ }
+ if (or->or_match_keys != NULL) {
+ ber_bvecfree (or->or_match_keys);
+ or->or_match_keys = NULL;
+ }
+ if (or->or_index_keys != NULL) {
+ ber_bvecfree (or->or_index_keys);
+ or->or_index_keys = NULL;
+ }
+ if (or->or_indexer != NULL) {
+ indexer_free (or->or_indexer);
+ or->or_indexer = NULL;
+ }
+ slapi_ch_free((void**)&or);
+ }
+ return 0;
+}
+
+#define MAX_CHAR_COMBINING 3
+/* The maximum number of Unicode characters that may combine
+ to form a single collation element.
+*/
+
+static int
+ss_match (struct berval* value,
+ const struct berval* key0,
+ indexer_t* ix)
+/* returns: 0 a prefix of value matched key
+ * 1 a subsequent substring might match; try again
+ * -1 nothing in value will match; give up
+ */
+{
+ auto struct berval* vals[2];
+ auto struct berval val;
+ auto struct berval key;
+ auto size_t attempts = MAX_CHAR_COMBINING;
+
+ vals[0] = &val;
+ vals[1] = NULL;
+ val.bv_val = value->bv_val;
+ val.bv_len = 0;
+ key.bv_val = key0->bv_val;
+ key.bv_len = key0->bv_len - 1;
+ while (1) {
+ auto struct berval** vkeys = ix->ix_index (ix, vals, NULL);
+ if (vkeys && vkeys[0]) {
+ auto const struct berval* vkey = vkeys[0];
+ if (vkey->bv_len > key.bv_len) {
+ if (--attempts <= 0) {
+ break; /* No match at this starting point */
+ } /* else Try looking at another character;
+ it may combine, and produce a shorter key.
+ */
+ } else if (SLAPI_BERVAL_EQ (vkey, &key)) {
+ value->bv_len -= val.bv_len;
+ value->bv_val += val.bv_len;
+ return 0;
+ }
+ }
+ if (val.bv_len >= value->bv_len) {
+ break;
+ }
+ val.bv_len += LDAP_UTF8LEN (val.bv_val + val.bv_len);
+ }
+ if (value->bv_len > 0) {
+ auto size_t one = LDAP_UTF8LEN (value->bv_val);
+ value->bv_len -= one;
+ value->bv_val += one;
+ return 1;
+ }
+ return -1;
+}
+
+static int
+ss_filter_match (or_filter_t* or, struct berval** vals)
+/* returns: 0 filter matched
+ * -1 filter did not match
+ * >0 an LDAP error code
+ */
+{
+ auto int rc = -1; /* no match */
+ auto indexer_t* ix = or->or_indexer;
+ if (vals != NULL) for (; *vals; ++vals) {
+ auto struct berval v;
+ auto struct berval** k = or->or_match_keys;
+ if (k == NULL || *k == NULL) {
+ rc = 0; /* present */
+ break;
+ }
+ v.bv_len = (*vals)->bv_len;
+ v.bv_val = (*vals)->bv_val;
+ if ((*k)->bv_len > 0 && ss_match (&v, *k, ix) != 0) {
+ break; /* initial failed */
+ }
+ rc = 0; /* so far, so good */
+ while (*++k) {
+ if ((*k)->bv_len <= 0) {
+ rc = 0;
+ } else if (k[1]) { /* middle */
+ do {
+ rc = ss_match (&v, *k, ix);
+ } while (rc > 0);
+ if (rc < 0) {
+ break;
+ }
+ } else { /* final */
+ auto size_t attempts = MAX_CHAR_COMBINING;
+ auto char* limit = v.bv_val;
+ auto struct berval** vkeys;
+ auto struct berval* vals[2];
+ auto struct berval key;
+ rc = -1;
+ vals[0] = &v;
+ vals[1] = NULL;
+ key.bv_val = (*k)->bv_val;
+ key.bv_len = (*k)->bv_len - 1;
+ v.bv_val = (*vals)->bv_val + (*vals)->bv_len;
+ while(1) {
+ v.bv_len = (*vals)->bv_len - (v.bv_val - (*vals)->bv_val);
+ vkeys = ix->ix_index (ix, vals, NULL);
+ if (vkeys && vkeys[0]) {
+ auto const struct berval* vkey = vkeys[0];
+ if (vkey->bv_len > key.bv_len) {
+ if (--attempts <= 0) {
+ break;
+ } /* else Try looking at another character;
+ it may combine, and produce a shorter key.
+ */
+ } else if (SLAPI_BERVAL_EQ (vkey, &key)) {
+ rc = 0;
+ break;
+ }
+ }
+ if (v.bv_val <= limit) break;
+ LDAP_UTF8DEC (v.bv_val);
+ }
+ break;
+ }
+ }
+ if (rc != -1) break;
+ }
+ return rc;
+}
+
+static int
+op_filter_match (or_filter_t* or, struct berval** vals)
+{
+ auto indexer_t* ix = or->or_indexer;
+ auto struct berval** v = ix->ix_index (ix, vals, NULL);
+ if (v != NULL) for (; *v; ++v) {
+ auto struct berval** k = or->or_match_keys;
+ if (k != NULL) for (; *k; ++k) {
+ switch (or->or_op) {
+ case SLAPI_OP_LESS:
+ if (slapi_berval_cmp (*v, *k) < 0) return 0; break;
+ case SLAPI_OP_LESS_OR_EQUAL:
+ if (slapi_berval_cmp (*v, *k) <= 0) return 0; break;
+ case SLAPI_OP_EQUAL:
+ if (SLAPI_BERVAL_EQ (*v, *k)) return 0; break;
+ case SLAPI_OP_GREATER_OR_EQUAL:
+ if (slapi_berval_cmp (*v, *k) >= 0) return 0; break;
+ case SLAPI_OP_GREATER:
+ if (slapi_berval_cmp (*v, *k) > 0) return 0; break;
+ default:
+ break;
+ }
+ }
+ }
+ return -1;
+}
+
+static int
+or_filter_match (void* obj, Slapi_Entry* entry, Slapi_Attr* attr)
+/* returns: 0 filter matched
+ * -1 filter did not match
+ * >0 an LDAP error code
+ */
+{
+ auto int rc = -1; /* no match */
+ auto or_filter_t* or = (or_filter_t*)obj;
+ for (; attr != NULL; slapi_entry_next_attr (entry, attr, &attr)) {
+ auto char* type = NULL;
+ auto struct berval** vals = NULL;
+
+/*
+ * XXXmcs 1-March-2001: This code would perform better if it did not make
+ * a copy of the values here, but that would require re-writing the code
+ * in this file to use Slapi_ValueSet's instead of struct berval **'s
+ * (and that is not a small project).
+ */
+ if (!slapi_attr_get_type (attr, &type) && type != NULL &&
+ !slapi_attr_type_cmp (or->or_type, type, 2/*match subtypes*/) &&
+ !slapi_attr_get_bervals_copy(attr, &vals) && vals != NULL) {
+
+ if (or->or_op == SLAPI_OP_SUBSTRING) {
+ rc = ss_filter_match (or, vals);
+ } else {
+ rc = op_filter_match (or, vals);
+ }
+
+ ber_bvecfree( vals );
+ vals = NULL;
+ if (rc >= 0) break;
+ }
+ }
+ return rc;
+}
+
+#define WILDCARD '*'
+/* If you want a filter value to contain a non-wildcard '*' or '\'
+ you write "\2a" or "\5c" (the ASCII codes, in hexadecimal).
+ For example, "4\2a4*flim\5cflam"
+ matches a value that begins with "4*4" and ends with "flim\flam"
+ (except that all the "\" should be doubled in C string literals).
+ This conforms to <draft-ietf-asid-ldapv3-attributes-08> section 8.3.
+*/
+
+static void
+ss_unescape (struct berval* val)
+{
+ char* s = val->bv_val;
+ char* t = s;
+ char* limit = s + val->bv_len;
+ while (s < limit) {
+ if (!memcmp (s, "\\2a", 3) ||
+ !memcmp (s, "\\2A", 3)) {
+ *t++ = WILDCARD;
+ s += 3;
+ } else if (!memcmp (s, "\\5c", 3) ||
+ !memcmp (s, "\\5C", 3)) {
+ *t++ = '\\';
+ s += 3;
+ } else {
+ if (t == s) LDAP_UTF8INC (t);
+ else t += LDAP_UTF8COPY (t, s);
+ LDAP_UTF8INC (s);
+ }
+ }
+ val->bv_len = t - val->bv_val;
+}
+
+static struct berval*
+slapi_ch_bvdup0 (struct berval* val)
+ /* Return a copy of val, with a 0 byte following the end. */
+{
+ auto struct berval* result = (struct berval*)
+ slapi_ch_malloc (sizeof (struct berval));
+ result->bv_len = val->bv_len;
+ result->bv_val = slapi_ch_malloc (result->bv_len + 1);
+ if (result->bv_len > 0) {
+ memcpy (result->bv_val, val->bv_val, result->bv_len);
+ }
+ result->bv_val[result->bv_len] = '\0';
+ return result;
+}
+
+static struct berval*
+ss_filter_value (const char* s, const size_t len, struct berval* val)
+{
+ val->bv_len = len;
+ if (len > 0) memcpy (val->bv_val, s, len);
+ ss_unescape (val);
+ return slapi_ch_bvdup0 (val);
+}
+
+static struct berval**
+ss_filter_values (struct berval* pattern, int* query_op)
+ /* Split the pattern into its substrings and return them. */
+{
+ auto struct berval** result;
+ auto struct berval val;
+ auto size_t n;
+ auto char* s;
+ auto char* p;
+ auto char* plimit = pattern->bv_val + pattern->bv_len;
+
+ /* Compute the length of the result array, and
+ the maximum bv_len of any of its elements. */
+ val.bv_len = 0;
+ n = 2; /* one key, plus NULL terminator */
+ s = pattern->bv_val;
+ for (p = s; p < plimit; LDAP_UTF8INC(p)) {
+ switch (*p) {
+ case WILDCARD:
+ ++n;
+ {
+ auto const size_t len = (p - s);
+ if (val.bv_len < len)
+ val.bv_len = len;
+ }
+ while (++p != plimit && *p == WILDCARD);
+ s = p;
+ break;
+ default: break;
+ }
+ }
+ if (n == 2) { /* no wildcards in pattern */
+ auto struct berval** pvec = (struct berval**) slapi_ch_malloc (sizeof (struct berval*) * 2);
+ auto struct berval* pv = (struct berval*) slapi_ch_malloc (sizeof (struct berval));
+ pvec[0] = pv;
+ pvec[1] = NULL;
+ pv->bv_len = pattern->bv_len;
+ pv->bv_val = slapi_ch_malloc (pv->bv_len);
+ memcpy (pv->bv_val, pattern->bv_val, pv->bv_len);
+ ss_unescape (pv);
+ *query_op = SLAPI_OP_EQUAL;
+ return pvec;
+ } else if (n == 3 && pattern->bv_len <= 1) { /* entire pattern is one wildcard */
+ return NULL; /* presence */
+ }
+ {
+ auto const size_t len = (p - s);
+ if (val.bv_len < len)
+ val.bv_len = len;
+ }
+ result = (struct berval**) slapi_ch_malloc (n * sizeof (struct berval*));
+ val.bv_val = slapi_ch_malloc (val.bv_len);
+ n = 0;
+ s = pattern->bv_val;
+ for (p = s; p < plimit; LDAP_UTF8INC(p)) {
+ switch (*p) {
+ case WILDCARD:
+ result[n++] = ss_filter_value (s, p-s, &val);
+ while (++p != plimit && *p == WILDCARD);
+ s = p;
+ break;
+ default: break;
+ }
+ }
+ if (p != s || s == plimit) {
+ result[n++] = ss_filter_value (s, p-s, &val);
+ }
+ result[n] = NULL;
+ slapi_ch_free((void**)&val.bv_val);
+ return result;
+}
+
+static struct berval*
+ss_filter_key (indexer_t* ix, struct berval* val)
+{
+ struct berval* key = (struct berval*) slapi_ch_calloc (1, sizeof(struct berval));
+ if (val->bv_len > 0) {
+ struct berval** keys = NULL;
+ auto struct berval* vals[2];
+ vals[0] = val;
+ vals[1] = NULL;
+ keys = ix->ix_index (ix, vals, NULL);
+ if (keys && keys[0]) {
+ key->bv_len = keys[0]->bv_len + 1;
+ key->bv_val = slapi_ch_malloc (key->bv_len);
+ memcpy (key->bv_val, keys[0]->bv_val, keys[0]->bv_len);
+ key->bv_val[key->bv_len-1] = '\0';
+ }
+ }
+ return key;
+}
+
+static struct berval**
+ss_filter_keys (indexer_t* ix, struct berval** values)
+ /* Index the substrings and return the key values,
+ with an extra byte appended to each key, so that
+ an empty key definitely implies an absent value.
+ */
+{
+ auto struct berval** keys = NULL;
+ if (values != NULL) {
+ auto size_t n; /* how many substring values */
+ auto struct berval** val;
+ for (n=0,val=values; *val != NULL; ++n,++val);
+ keys = (struct berval**) slapi_ch_malloc ((n+1) * sizeof (struct berval*));
+ for (n=0,val=values; *val != NULL; ++n,++val) {
+ keys[n] = ss_filter_key (ix, *val);
+ }
+ keys[n] = NULL;
+ }
+ return keys;
+}
+
+static int or_filter_index (Slapi_PBlock* pb);
+
+static int
+or_filter_create (Slapi_PBlock* pb)
+{
+ auto int rc = LDAP_UNAVAILABLE_CRITICAL_EXTENSION; /* failed to initialize */
+ auto char* mrOID = NULL;
+ auto char* mrTYPE = NULL;
+ auto struct berval* mrVALUE = NULL;
+ auto or_filter_t* or = NULL;
+
+ if (!slapi_pblock_get (pb, SLAPI_PLUGIN_MR_OID, &mrOID) && mrOID != NULL &&
+ !slapi_pblock_get (pb, SLAPI_PLUGIN_MR_TYPE, &mrTYPE) && mrTYPE != NULL &&
+ !slapi_pblock_get (pb, SLAPI_PLUGIN_MR_VALUE, &mrVALUE) && mrVALUE != NULL) {
+ auto size_t len = mrVALUE->bv_len;
+ auto indexer_t* ix = NULL;
+ auto int op = SLAPI_OP_EQUAL;
+ auto struct berval bv;
+ auto int reusable = MRF_ANY_TYPE;
+
+ LDAPDebug (LDAP_DEBUG_FILTER, "=> or_filter_create(oid %s; type %s)\n",
+ mrOID, mrTYPE, 0);
+ if (len > 1 && (ix = indexer_create (mrOID)) != NULL) {
+ auto char* val = mrVALUE->bv_val;
+ switch (val[0]) {
+ case '=': break;
+ case '<': op = (val[1] == '=') ?
+ SLAPI_OP_LESS_OR_EQUAL : SLAPI_OP_LESS; break;
+ case '>': op = (val[1] == '=') ?
+ SLAPI_OP_GREATER_OR_EQUAL : SLAPI_OP_GREATER; break;
+ case WILDCARD: op = SLAPI_OP_SUBSTRING; break;
+ default:
+ break;
+ }
+ for (; len > 0 && *val != ' '; ++val, --len);
+ if (len > 0) ++val, --len; /* skip the space */
+ bv.bv_len = len;
+ bv.bv_val = (len > 0) ? val : NULL;
+ } else { /* mrOID does not identify an ordering rule. */
+ /* Is it an ordering rule OID with a relational operator suffix? */
+ auto size_t oidlen = strlen (mrOID);
+ if (oidlen > 2 && mrOID[oidlen-2] == '.') {
+ op = atoi (mrOID + oidlen - 1);
+ switch (op) {
+ case SLAPI_OP_LESS:
+ case SLAPI_OP_LESS_OR_EQUAL:
+ case SLAPI_OP_EQUAL:
+ case SLAPI_OP_GREATER_OR_EQUAL:
+ case SLAPI_OP_GREATER:
+ case SLAPI_OP_SUBSTRING:
+ {
+ auto char* or_oid = slapi_ch_strdup (mrOID);
+ or_oid [oidlen-2] = '\0';
+ ix = indexer_create (or_oid);
+ if (ix != NULL) {
+ memcpy (&bv, mrVALUE, sizeof(struct berval));
+ reusable |= MRF_ANY_VALUE;
+ }
+ slapi_ch_free((void**)&or_oid);
+ }
+ break;
+ default: /* not a relational operator */
+ break;
+ }
+ }
+ }
+ if (ix != NULL) {
+ or = (or_filter_t*) slapi_ch_calloc (1, sizeof (or_filter_t));
+ or->or_type = slapi_ch_strdup (mrTYPE);
+ or->or_indexer = ix;
+ or->or_op = op;
+ if (op == SLAPI_OP_SUBSTRING) {
+ or->or_values = ss_filter_values (&bv, &(or->or_op));
+ } else {
+ or->or_values = (struct berval**)
+ slapi_ch_malloc (2 * sizeof (struct berval*));
+ or->or_values[0] = slapi_ch_bvdup0 (&bv);
+ or->or_values[1] = NULL;
+ }
+ {
+ auto struct berval** val = or->or_values;
+ if (val) for (; *val; ++val) {
+ LDAPDebug (LDAP_DEBUG_FILTER, "value \"%s\"\n", (*val)->bv_val, 0, 0);
+ }
+ }
+ if (or->or_op == SLAPI_OP_SUBSTRING) {
+ or->or_match_keys = ss_filter_keys (ix, or->or_values);
+ } else {
+ or->or_match_keys = slapi_ch_bvecdup (
+ ix->ix_index (ix, or->or_values, NULL));
+ }
+ slapi_pblock_set (pb, SLAPI_PLUGIN_OBJECT, or);
+ slapi_pblock_set (pb, SLAPI_PLUGIN_DESTROY_FN, (void*)or_filter_destroy);
+ slapi_pblock_set (pb, SLAPI_PLUGIN_MR_FILTER_MATCH_FN, (void*)or_filter_match);
+ slapi_pblock_set (pb, SLAPI_PLUGIN_MR_FILTER_INDEX_FN, (void*)or_filter_index);
+/* slapi_pblock_set (pb, SLAPI_PLUGIN_MR_FILTER_REUSABLE, &reusable); */
+/* slapi_pblock_set (pb, SLAPI_PLUGIN_MR_FILTER_RESET_FN, ?); to be implemented */
+ rc = LDAP_SUCCESS;
+ }
+ } else {
+ LDAPDebug (LDAP_DEBUG_FILTER, "=> or_filter_create missing parameter(s)\n", 0, 0, 0);
+ }
+ LDAPDebug (LDAP_DEBUG_FILTER, "<= or_filter_create(%p) %i\n", (void*)or, rc, 0);
+ return rc;
+}
+
+static indexer_t*
+op_indexer_get (Slapi_PBlock* pb)
+{
+ auto void* obj = NULL;
+ if ( ! slapi_pblock_get (pb, SLAPI_PLUGIN_OBJECT, &obj)) {
+ return (indexer_t*)obj;
+ }
+ return NULL;
+}
+
+static int
+op_indexer_destroy (Slapi_PBlock* pb)
+{
+ auto indexer_t* ix = op_indexer_get (pb);
+ LDAPDebug (LDAP_DEBUG_FILTER, "op_indexer_destroy(%p)\n", (void*)ix, 0, 0);
+ if (ix != NULL) {
+ indexer_free (ix);
+ }
+ return 0;
+}
+
+static int
+op_index_entry (Slapi_PBlock* pb)
+ /* Compute collation keys (when writing an entry). */
+{
+ auto indexer_t* ix = op_indexer_get (pb);
+ auto int rc;
+ struct berval** values;
+ if (ix != NULL && ix->ix_index != NULL &&
+ !slapi_pblock_get (pb, SLAPI_PLUGIN_MR_VALUES, &values) &&
+ !slapi_pblock_set (pb, SLAPI_PLUGIN_MR_KEYS, ix->ix_index (ix, values, NULL))) {
+ rc = 0;
+ } else {
+ rc = LDAP_OPERATIONS_ERROR;
+ }
+ LDAPDebug (LDAP_DEBUG_FILTER, "op_index_entry(%p) %i\n", (void*)ix, rc, 0);
+ return rc;
+}
+
+static int
+op_index_search (Slapi_PBlock* pb)
+ /* Compute collation keys (when searching for entries). */
+{
+ auto or_filter_t* or = or_filter_get (pb);
+ auto int rc = LDAP_OPERATIONS_ERROR;
+ if (or != NULL) {
+ auto indexer_t* ix = or->or_indexer;
+ struct berval** values;
+ if (or->or_index_keys == NULL && ix != NULL && ix->ix_index != NULL &&
+ !slapi_pblock_get (pb, SLAPI_PLUGIN_MR_VALUES, &values)) {
+ or->or_index_keys = slapi_ch_bvecdup (
+ ix->ix_index (ix, values, NULL));
+ }
+ if (or->or_index_keys) {
+ rc = slapi_pblock_set (pb, SLAPI_PLUGIN_MR_KEYS, or->or_index_keys);
+ }
+ }
+ LDAPDebug (LDAP_DEBUG_FILTER, "op_index_search(%p) %i\n", (void*)or, rc, 0);
+ return rc;
+}
+
+typedef struct ss_indexer_t {
+ char* ss_oid; /* ss_indexer->ix_oid && ".6" */
+ indexer_t* ss_indexer;
+} ss_indexer_t;
+
+static void
+ss_indexer_free (ss_indexer_t* ss)
+{
+ slapi_ch_free((void**)&ss->ss_oid);
+ if (ss->ss_indexer != NULL) {
+ indexer_free (ss->ss_indexer);
+ ss->ss_indexer = NULL;
+ }
+ slapi_ch_free((void**)&ss);
+}
+
+static ss_indexer_t*
+ss_indexer_get (Slapi_PBlock* pb)
+{
+ auto void* obj = NULL;
+ if ( ! slapi_pblock_get (pb, SLAPI_PLUGIN_OBJECT, &obj)) {
+ return (ss_indexer_t*)obj;
+ }
+ return NULL;
+}
+
+static void
+ss_indexer_destroy (Slapi_PBlock* pb)
+{
+ auto ss_indexer_t* ss = ss_indexer_get (pb);
+ LDAPDebug (LDAP_DEBUG_FILTER, "ss_indexer_destroy(%p)\n", (void*)ss, 0, 0);
+ if (ss) {
+ ss_indexer_free (ss);
+ }
+}
+
+#define SS_INDEX_LENGTH 3 /* characters */
+
+static char ss_prefixI = '[';
+static char ss_prefixM = '|';
+static char ss_prefixF = '}';
+
+static struct berval ss_index_initial = {1, &ss_prefixI};
+static struct berval ss_index_middle = {1, &ss_prefixM};
+static struct berval ss_index_final = {1, &ss_prefixF};
+
+static int
+long_enough (struct berval* bval, size_t enough)
+{
+ if (bval) {
+ auto size_t len = 0;
+ auto char* next = bval->bv_val;
+ auto char* last = next + bval->bv_len;
+ while (next < last) {
+ LDAP_UTF8INC (next);
+ if (++len >= enough) {
+ if (next > last) next = last;
+ bval->bv_len = next - bval->bv_val;
+ return 1;
+ }
+ }
+ }
+ return !enough;
+}
+
+static int
+ss_index_entry (Slapi_PBlock* pb)
+ /* Compute substring index keys (when writing an entry). */
+{
+ auto int rc = LDAP_OPERATIONS_ERROR;
+ auto size_t substringsLen = 0;
+ struct berval** values;
+ auto ss_indexer_t* ss = ss_indexer_get (pb);
+ auto indexer_t* ix = ss ? ss->ss_indexer : NULL;
+ if (ix != NULL && ix->ix_index != NULL &&
+ !slapi_pblock_get (pb, SLAPI_PLUGIN_MR_VALUES, &values)) {
+ auto struct berval* substrings = NULL;
+ auto struct berval** prefixes = NULL;
+ auto struct berval** value;
+ for (value = values; *value != NULL; ++value) {
+ auto struct berval substring;
+ substring.bv_val = (*value)->bv_val;
+ substring.bv_len = (*value)->bv_len;
+ if (long_enough (&substring, SS_INDEX_LENGTH-1)) {
+ auto struct berval* prefix = &ss_index_initial;
+ auto size_t offset;
+ for (offset = 0; 1; ++offset) {
+ ++substringsLen;
+ substrings = (struct berval*)
+ slapi_ch_realloc ((void*)substrings, substringsLen * sizeof(struct berval));
+ memcpy (&(substrings[substringsLen-1]), &substring, sizeof (struct berval));
+ prefixes = (struct berval**)
+ slapi_ch_realloc ((void*)prefixes, substringsLen * sizeof(struct berval*));
+ prefixes[substringsLen-1] = prefix;
+
+ if (offset != 0) LDAP_UTF8INC (substring.bv_val);
+ substring.bv_len = (*value)->bv_len - (substring.bv_val - (*value)->bv_val);
+ if (long_enough (&substring, SS_INDEX_LENGTH)) {
+ prefix = &ss_index_middle;
+ } else if (long_enough (&substring, SS_INDEX_LENGTH-1)) {
+ prefix = &ss_index_final;
+ } else {
+ break;
+ }
+ }
+ }
+ }
+ if (substrings != NULL) {
+ auto struct berval** vector = (struct berval**)
+ slapi_ch_malloc ((substringsLen+1) * sizeof(struct berval*));
+ auto size_t i;
+ for (i = 0; i < substringsLen; ++i) vector[i] = &(substrings[i]);
+ vector[substringsLen] = NULL;
+ rc = slapi_pblock_set (pb, SLAPI_PLUGIN_MR_KEYS, ix->ix_index (ix, vector, prefixes));
+ slapi_ch_free((void**)&vector);
+ slapi_ch_free((void**)&substrings);
+ slapi_ch_free((void**)&prefixes);
+ }
+ }
+ LDAPDebug (LDAP_DEBUG_FILTER, "ss_index_entry(%p) %i %lu substrings\n",
+ (void*)ss, rc, (unsigned long)substringsLen);
+ return rc;
+}
+
+static int
+ss_index_search (Slapi_PBlock* pb)
+ /* Compute substring search keys (when searching for entries). */
+{
+ auto int rc = LDAP_OPERATIONS_ERROR;
+ auto or_filter_t* or = or_filter_get (pb);
+ if (or) {
+ if (or->or_index_keys == NULL /* not yet computed */ &&
+ or->or_values && or->or_indexer && or->or_indexer->ix_index) {
+ auto size_t substringsLen = 0;
+ auto struct berval* substrings = NULL;
+ auto struct berval** prefixes = NULL;
+ auto struct berval** value;
+ for (value = or->or_values; *value != NULL; ++value) {
+ auto size_t offset;
+ auto struct berval substring;
+ substring.bv_val = (*value)->bv_val;
+ for (offset = 0; 1; ++offset, LDAP_UTF8INC (substring.bv_val)) {
+ auto struct berval* prefix = NULL;
+ substring.bv_len = (*value)->bv_len - (substring.bv_val - (*value)->bv_val);
+ if (offset == 0 && value == or->or_values) {
+ if (long_enough (&substring, SS_INDEX_LENGTH - 1)) {
+ prefix = &ss_index_initial;
+ }
+ } else if (value[1] != NULL) {
+ if (long_enough (&substring, SS_INDEX_LENGTH)) {
+ prefix = &ss_index_middle;
+ }
+ } else if (long_enough (&substring, SS_INDEX_LENGTH)) {
+ prefix = &ss_index_middle;
+ } else if (long_enough (&substring, SS_INDEX_LENGTH-1)) {
+ prefix = &ss_index_final;
+ }
+ if (prefix == NULL) break;
+ ++substringsLen;
+ substrings = (struct berval*)
+ slapi_ch_realloc ((void*)substrings, substringsLen * sizeof(struct berval));
+ memcpy (&(substrings[substringsLen-1]), &substring, sizeof (struct berval));
+ prefixes = (struct berval**)
+ slapi_ch_realloc ((void*)prefixes, substringsLen * sizeof(struct berval*));
+ prefixes[substringsLen-1] = prefix;
+ }
+ }
+ if (substrings != NULL) {
+ auto indexer_t* ix = or->or_indexer;
+ auto struct berval** vector = (struct berval**)
+ slapi_ch_malloc ((substringsLen+1) * sizeof(struct berval*));
+ auto size_t i;
+ for (i = 0; i < substringsLen; ++i) vector[i] = &(substrings[i]);
+ vector[substringsLen] = NULL;
+ or->or_index_keys = slapi_ch_bvecdup (
+ ix->ix_index (ix, vector, prefixes));
+ slapi_ch_free((void**)&vector);
+ slapi_ch_free((void**)&substrings);
+ slapi_ch_free((void**)&prefixes);
+ }
+ }
+ if (or->or_index_keys) {
+ rc = slapi_pblock_set (pb, SLAPI_PLUGIN_MR_KEYS, or->or_index_keys);
+ }
+ }
+ LDAPDebug (LDAP_DEBUG_FILTER, "ss_index_search(%p) %i\n", (void*)or, rc, 0);
+ return rc;
+}
+
+static int
+ss_indexable (struct berval** values)
+ /* at least one of the values is long enough to index */
+{
+ auto struct berval** val = values;
+ if (val) for (; *val; ++val) {
+ auto struct berval value;
+ value.bv_val = (*val)->bv_val;
+ value.bv_len = (*val)->bv_len;
+ if (val == values) { /* initial */
+ if (long_enough (&value, SS_INDEX_LENGTH-1)) return 1;
+ } else if (val[1]) { /* middle */
+ if (long_enough (&value, SS_INDEX_LENGTH)) return 1;
+ } else { /* final */
+ if (long_enough (&value, SS_INDEX_LENGTH-1)) return 1;
+ }
+ }
+ return 0;
+}
+
+static struct berval ss_one_berval = {0,0};
+static struct berval* ss_one_value[2] = {&ss_one_berval, NULL};
+
+static int
+or_filter_index (Slapi_PBlock* pb)
+ /* Return an indexer and values that accelerate the given filter. */
+{
+ auto or_filter_t* or = or_filter_get (pb);
+ auto int rc = LDAP_UNAVAILABLE_CRITICAL_EXTENSION;
+ auto IFP mrINDEX_FN = NULL;
+ auto struct berval** mrVALUES = NULL;
+ auto char* mrOID = NULL;
+ auto int mrQUERY_OPERATOR;
+ if (or && or->or_indexer && or->or_indexer->ix_index) {
+ switch (or->or_op) {
+ case SLAPI_OP_LESS:
+ case SLAPI_OP_LESS_OR_EQUAL:
+ case SLAPI_OP_EQUAL:
+ case SLAPI_OP_GREATER_OR_EQUAL:
+ case SLAPI_OP_GREATER:
+ mrINDEX_FN = op_index_search;
+ mrVALUES = or->or_values;
+ mrOID = or->or_indexer->ix_oid;
+ mrQUERY_OPERATOR = or->or_op;
+ break;
+ case SLAPI_OP_SUBSTRING:
+ if (ss_indexable (or->or_values)) {
+ if (or->or_oid == NULL) {
+ auto const size_t len = strlen (or->or_indexer->ix_oid);
+ or->or_oid = slapi_ch_malloc (len + 3);
+ memcpy (or->or_oid, or->or_indexer->ix_oid, len);
+ sprintf (or->or_oid + len, ".%1i", SLAPI_OP_SUBSTRING);
+ }
+ mrINDEX_FN = ss_index_search;
+ mrVALUES = ss_one_value;
+ mrOID = or->or_oid;
+ mrQUERY_OPERATOR = SLAPI_OP_EQUAL;
+ }
+ break;
+ default: /* unsupported operator */
+ break;
+ }
+ }
+ if (mrINDEX_FN != NULL &&
+ !(rc = slapi_pblock_set (pb, SLAPI_PLUGIN_OBJECT, or)) &&
+ !(rc = slapi_pblock_set (pb, SLAPI_PLUGIN_MR_TYPE, or->or_type)) &&
+ !(rc = slapi_pblock_set (pb, SLAPI_PLUGIN_MR_INDEX_FN, (void*)mrINDEX_FN)) &&
+ !(rc = slapi_pblock_set (pb, SLAPI_PLUGIN_MR_VALUES, mrVALUES)) &&
+ !(rc = slapi_pblock_set (pb, SLAPI_PLUGIN_MR_OID, mrOID))) {
+ rc = slapi_pblock_set (pb, SLAPI_PLUGIN_MR_QUERY_OPERATOR, &mrQUERY_OPERATOR);
+ }
+ LDAPDebug (LDAP_DEBUG_FILTER, "or_filter_index(%p) %i\n",
+ (void*)(or ? or->or_indexer : NULL), rc, 0);
+ return rc;
+}
+
+static int
+or_indexer_create (Slapi_PBlock* pb)
+{
+ auto int rc = LDAP_UNAVAILABLE_CRITICAL_EXTENSION; /* failed to initialize */
+ auto char* mrOID = NULL;
+ auto void* mrOBJECT = NULL;
+ if (slapi_pblock_get (pb, SLAPI_PLUGIN_MR_OID, &mrOID) || mrOID == NULL) {
+ LDAPDebug (LDAP_DEBUG_FILTER, "=> or_indexer_create: no OID parameter\n", 0, 0, 0);
+ } else {
+ auto indexer_t* ix = indexer_create (mrOID);
+ auto char* mrTYPE = NULL;
+ slapi_pblock_get (pb, SLAPI_PLUGIN_MR_TYPE, &mrTYPE);
+ LDAPDebug (LDAP_DEBUG_FILTER, "=> or_indexer_create(oid %s; type %s)\n",
+ mrOID, mrTYPE ? mrTYPE : "<NULL>", 0);
+ if (ix != NULL) {
+ if (ix->ix_index != NULL &&
+ !slapi_pblock_set (pb, SLAPI_PLUGIN_OBJECT, ix) &&
+ !slapi_pblock_set (pb, SLAPI_PLUGIN_MR_OID, ix->ix_oid) &&
+ !slapi_pblock_set (pb, SLAPI_PLUGIN_MR_INDEX_FN, (void*)op_index_entry) &&
+ !slapi_pblock_set (pb, SLAPI_PLUGIN_DESTROY_FN, (void*)op_indexer_destroy)) {
+ mrOBJECT = ix;
+ rc = 0; /* success */
+ } else {
+ indexer_free (ix);
+ }
+ } else { /* mrOID does not identify an ordering rule. */
+ /* Is it an ordering rule OID with the substring suffix? */
+ auto size_t oidlen = strlen (mrOID);
+ if (oidlen > 2 && mrOID[oidlen-2] == '.' &&
+ atoi (mrOID + oidlen - 1) == SLAPI_OP_SUBSTRING) {
+ auto char* or_oid = slapi_ch_strdup (mrOID);
+ or_oid [oidlen-2] = '\0';
+ ix = indexer_create (or_oid);
+ if (ix != NULL) {
+ auto ss_indexer_t* ss = (ss_indexer_t*) slapi_ch_malloc (sizeof (ss_indexer_t));
+ ss->ss_indexer = ix;
+ oidlen = strlen (ix->ix_oid);
+ ss->ss_oid = slapi_ch_malloc (oidlen + 3);
+ memcpy (ss->ss_oid, ix->ix_oid, oidlen);
+ sprintf (ss->ss_oid + oidlen, ".%1i", SLAPI_OP_SUBSTRING);
+ if (ix->ix_index != NULL &&
+ !slapi_pblock_set (pb, SLAPI_PLUGIN_OBJECT, ss) &&
+ !slapi_pblock_set (pb, SLAPI_PLUGIN_MR_OID, ss->ss_oid) &&
+ !slapi_pblock_set (pb, SLAPI_PLUGIN_MR_INDEX_FN, (void*)ss_index_entry) &&
+ !slapi_pblock_set (pb, SLAPI_PLUGIN_DESTROY_FN, (void*)ss_indexer_destroy)) {
+ mrOBJECT = ss;
+ rc = 0; /* success */
+ } else {
+ ss_indexer_free (ss);
+ }
+ }
+ slapi_ch_free((void**)&or_oid);
+ }
+ }
+ }
+ LDAPDebug (LDAP_DEBUG_FILTER, "<= or_indexer_create(%p) %i\n", mrOBJECT, rc, 0);
+ return rc;
+}
+
+static Slapi_PluginDesc pdesc = { "orderingrule", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT,
+ "internationalized ordering rule plugin" };
+
+#define SLAPI_ORPLUGIN_NAME pdesc.spd_description
+
+int /* LDAP error code */
+orderingRule_init (Slapi_PBlock* pb)
+{
+ int rc;
+ int argc;
+ char** argv;
+ char* cfgpath;
+
+/* if (!(rc = slapi_pblock_set (pb, SLAPI_PLUGIN_PRIVATE, ...)) &&
+ !(rc = slapi_pblock_set (pb, SLAPI_PLUGIN_CLOSE_FN, ...)))
+*/
+
+#ifdef USE_HPUX_CC
+ /* not needed with ICU
+ shl_load ( "../lib/libnsbrk30.sl", BIND_IMMEDIATE, 0L );
+ shl_load ( "../lib/libnscnv30.sl", BIND_IMMEDIATE, 0L );
+ shl_load ( "../lib/libnscol30.sl", BIND_IMMEDIATE, 0L );
+ shl_load ( "../lib/libnsfmt30.sl", BIND_IMMEDIATE, 0L );
+ shl_load ( "../lib/libnsres30.sl", BIND_IMMEDIATE, 0L );
+ shl_load ( "../lib/libnsuni30.sl", BIND_IMMEDIATE, 0L );
+ */
+#endif
+
+ if ( slapi_pblock_get( pb, SLAPI_CONFIG_DIRECTORY, &cfgpath ) != 0 ) {
+ slapi_log_error( SLAPI_LOG_FATAL, SLAPI_ORPLUGIN_NAME,
+ "Unable to retrieve slapd configuration pathname; using default\n" );
+ cfgpath = NULL;
+ }
+
+ collation_init( cfgpath );
+ if (!slapi_pblock_get (pb, SLAPI_PLUGIN_ARGC, &argc) &&
+ !slapi_pblock_get (pb, SLAPI_PLUGIN_ARGV, &argv) &&
+ argc > 0) {
+ collation_read_config (argv[0]);
+ }
+ {
+ slapi_pblock_set (pb, SLAPI_PLUGIN_MR_INDEXER_CREATE_FN, (void*)or_indexer_create);
+ rc = slapi_pblock_set (pb, SLAPI_PLUGIN_MR_FILTER_CREATE_FN, (void*)or_filter_create);
+ }
+ if ( rc == 0 ) {
+ rc = slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&pdesc );
+ }
+ LDAPDebug (LDAP_DEBUG_FILTER, "orderingRule_init %i\n", rc, 0, 0);
+ return rc;
+}
diff --git a/ldap/servers/plugins/collation/orfilter.h b/ldap/servers/plugins/collation/orfilter.h
new file mode 100644
index 00000000..85a32592
--- /dev/null
+++ b/ldap/servers/plugins/collation/orfilter.h
@@ -0,0 +1,10 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#ifndef _ORFILTER_H_
+#define _ORFILTER_H_
+
+#endif
diff --git a/ldap/servers/plugins/cos/Makefile b/ldap/servers/plugins/cos/Makefile
new file mode 100644
index 00000000..de540f5a
--- /dev/null
+++ b/ldap/servers/plugins/cos/Makefile
@@ -0,0 +1,79 @@
+#
+# BEGIN COPYRIGHT BLOCK
+# Copyright 2001 Sun Microsystems, Inc.
+# Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+# All rights reserved.
+# END COPYRIGHT BLOCK
+#
+LDAP_SRC = ../../..
+MCOM_ROOT = ../../../../..
+
+NOSTDCLEAN=true # don't let nsconfig.mk define target clean
+NOSTDSTRIP=true # don't let nsconfig.mk define target strip
+NSPR20=true # probably should be defined somewhere else (not sure where)
+
+OBJDEST = $(OBJDIR)/lib/libcos
+LIBDIR = $(LIB_RELDIR)
+
+include $(MCOM_ROOT)/ldapserver/nsdefs.mk
+include $(MCOM_ROOT)/ldapserver/nsconfig.mk
+include $(LDAP_SRC)/nsldap.mk
+
+ifeq ($(ARCH), WINNT)
+DEF_FILE:=./cos.def
+endif
+
+COS_OBJS = cos.o cos_cache.o
+OBJS = $(addprefix $(OBJDEST)/, $(COS_OBJS))
+
+COS_DLL = cos-plugin
+
+INCLUDES += -I../../slapd -I../../../include
+CFLAGS+=$(SLCFLAGS) -DSLAPD_LOGGING
+
+ifeq ($(ARCH), WINNT)
+EXTRA_LIBS_DEP += $(LIBSLAPD) $(NSPR_DEP) $(LDAPSDK_DEP)
+EXTRA_LIBS += $(NSPRLINK) $(LIBSLAPD) $(LDAP_SDK_LIBLDAP_DLL)
+COS_DLL_OBJ = $(addprefix $(OBJDEST)/, dllmain.o)
+endif
+
+ifeq ($(ARCH), AIX)
+EXTRA_LIBS_DEP += $(LIBSLAPD) $(NSPR_DEP) $(LDAPSDK_DEP)
+EXTRA_LIBS += $(LIBSLAPDLINK) $(NSPRLINK) $(LDAP_SDK_LIBLDAP_DLL)
+LD=ld
+endif
+
+ifeq ($(ARCH), HPUX)
+EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAPSDK_DEP) $(NSPR_DEP) $(SECURITY_DEP)
+EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(SECURITYLINK) $(NSPRLINK) $(ICULINK)
+endif
+
+COS= $(addprefix $(LIBDIR)/, $(COS_DLL).$(DLL_SUFFIX))
+
+clientSDK:
+
+all: $(OBJDEST) $(LIBDIR) $(COS)
+
+ifeq ($(ARCH), WINNT)
+$(COS): $(OBJS) $(COS_DLL_OBJ) $(DEF_FILE)
+ $(LINK_DLL) $(COS_DLL_OBJ) $(EXTRA_LIBS) /DEF:$(DEF_FILE)
+else
+$(COS): $(OBJS) $(COS_DLL_OBJ)
+ $(LINK_DLL) $(COS_DLL_OBJ) $(EXTRA_LIBS)
+endif
+
+
+veryclean: clean
+
+clean:
+ $(RM) $(OBJS)
+ifeq ($(ARCH), WINNT)
+ $(RM) $(COS_DLL_OBJ)
+endif
+ $(RM) $(COS)
+
+$(OBJDEST):
+ $(MKDIR) $(OBJDEST)
+
+$(LIBDIR):
+ $(MKDIR) $(LIBDIR)
diff --git a/ldap/servers/plugins/cos/cos.c b/ldap/servers/plugins/cos/cos.c
new file mode 100644
index 00000000..5a077646
--- /dev/null
+++ b/ldap/servers/plugins/cos/cos.c
@@ -0,0 +1,266 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include <stdio.h>
+#include <string.h>
+#include "portable.h"
+#include "nspr.h"
+#include "slapi-plugin.h"
+#include "slapi-private.h"
+#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */
+#include "dirver.h"
+#include "cos_cache.h"
+#include "vattr_spi.h"
+
+/* get file mode flags for unix */
+#ifndef _WIN32
+#include <sys/stat.h>
+#endif
+
+/*** secret slapd stuff ***/
+
+/*
+ these are required here because they are not available
+ in any public header. They must exactly match their
+ counterparts in the server or they will fail to work
+ correctly.
+*/
+
+/*** from proto-slap.h ***/
+
+int slapd_log_error_proc( char *subsystem, char *fmt, ... );
+
+/*** from ldaplog.h ***/
+
+/* edited ldaplog.h for LDAPDebug()*/
+#ifndef _LDAPLOG_H
+#define _LDAPLOG_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define LDAP_DEBUG_TRACE 0x00001 /* 1 */
+#define LDAP_DEBUG_ANY 0x04000 /* 16384 */
+#define LDAP_DEBUG_PLUGIN 0x10000 /* 65536 */
+
+/* debugging stuff */
+# ifdef _WIN32
+ extern int *module_ldap_debug;
+# define LDAPDebug( level, fmt, arg1, arg2, arg3 ) \
+ { \
+ if ( *module_ldap_debug & level ) { \
+ slapd_log_error_proc( NULL, fmt, arg1, arg2, arg3 ); \
+ } \
+ }
+# else /* _WIN32 */
+ extern int slapd_ldap_debug;
+# define LDAPDebug( level, fmt, arg1, arg2, arg3 ) \
+ { \
+ if ( slapd_ldap_debug & level ) { \
+ slapd_log_error_proc( NULL, fmt, arg1, arg2, arg3 ); \
+ } \
+ }
+# endif /* Win32 */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _LDAP_H */
+
+/*** end secrets ***/
+
+#define COS_PLUGIN_SUBSYSTEM "cos-plugin" /* used for logging */
+
+/* subrelease in the following version info is for odd-ball cos releases
+ * which do not fit into a general release, this can be used for beta releases
+ * and other (this version stuff is really to help outside applications which
+ * may wish to update cos decide whether the cos version they want to update to
+ * is a higher release than the installed plugin)
+ *
+ * note: release origin is 00 for directory server
+ * sub-release should be:
+ * 50 for initial RTM products
+ * from 0 increasing for alpha/beta releases
+ * from 51 increasing for patch releases
+ */
+#define COS_VERSION 0x00050050 /* version format: 0x release origin 00 major 05 minor 00 sub-release 00 */
+
+/* other function prototypes */
+int cos_init( Slapi_PBlock *pb );
+int cos_compute(computed_attr_context *c,char* type,Slapi_Entry *e,slapi_compute_output_t outputfn);
+int cos_start( Slapi_PBlock *pb );
+int cos_close( Slapi_PBlock *pb );
+int cos_post_op( Slapi_PBlock *pb );
+
+
+static Slapi_PluginDesc pdesc = { "cos", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT,
+ "class of service plugin" };
+
+static void * cos_plugin_identity = NULL;
+
+
+#ifdef _WIN32
+int *module_ldap_debug = 0;
+
+void plugin_init_debug_level(int *level_ptr)
+{
+ module_ldap_debug = level_ptr;
+}
+#endif
+
+/*
+** Plugin identity mgmt
+*/
+
+void cos_set_plugin_identity(void * identity)
+{
+ cos_plugin_identity=identity;
+}
+
+void * cos_get_plugin_identity()
+{
+ return cos_plugin_identity;
+}
+
+int cos_version()
+{
+ return COS_VERSION;
+}
+
+/*
+ cos_init
+ --------
+ adds our callbacks to the list
+*/
+int cos_init( Slapi_PBlock *pb )
+{
+ int ret = 0;
+ void * plugin_identity=NULL;
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_init\n",0,0,0);
+
+ /*
+ ** Store the plugin identity for later use.
+ ** Used for internal operations
+ */
+
+ slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &plugin_identity);
+ PR_ASSERT (plugin_identity);
+ cos_set_plugin_identity(plugin_identity);
+
+ if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION,
+ SLAPI_PLUGIN_VERSION_01 ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN,
+ (void *) cos_start ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODIFY_FN,
+ (void *) cos_post_op ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODRDN_FN,
+ (void *) cos_post_op ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_POST_ADD_FN,
+ (void *) cos_post_op ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_POST_DELETE_FN,
+ (void *) cos_post_op ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN,
+ (void *) cos_close ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION,
+ (void *)&pdesc ) != 0 )
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, COS_PLUGIN_SUBSYSTEM,
+ "cos_init: failed to register plugin\n" );
+ ret = -1;
+ }
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_init\n",0,0,0);
+ return ret;
+}
+
+
+/*
+ cos_start
+ ---------
+ This function registers the computed attribute evaluator
+ and inits the cos cache.
+ It is called after cos_init.
+*/
+int cos_start( Slapi_PBlock *pb )
+{
+ int ret = 0;
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_start\n",0,0,0);
+
+ if( !cos_cache_init() )
+ {
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "cos: ready for service\n",0,0,0);
+ }
+ else
+ {
+ /* problems we are hosed */
+ cos_cache_stop();
+ LDAPDebug( LDAP_DEBUG_ANY, "cos_start: failed to initialise\n",0,0,0);
+ ret = -1;
+ }
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_start\n",0,0,0);
+ return ret;
+}
+
+/*
+ cos_close
+ ---------
+ closes down the cache
+*/
+int cos_close( Slapi_PBlock *pb )
+{
+ LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_close\n",0,0,0);
+
+ cos_cache_stop();
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_close\n",0,0,0);
+
+ return 0;
+}
+
+/*
+ cos_compute
+ -----------
+ called when evaluating named attributes in a search
+ and attributes remain unfound in the entry,
+ this function checks the attribute for a match with
+ those in the class of service definitions, and if a
+ match is found, adds the attribute and value to the
+ output list
+
+ returns
+ 0 on success
+ 1 on outright failure
+ -1 when doesn't know about attribute
+*/
+int cos_compute(computed_attr_context *c,char* type,Slapi_Entry *e,slapi_compute_output_t outputfn)
+{
+ int ret = -1;
+
+ return ret;
+}
+
+
+/*
+ cos_post_op
+ -----------
+ Catch all for all post operations that change entries
+ in some way - this simply notifies the cache of a
+ change - the cache decides if action is necessary
+*/
+int cos_post_op( Slapi_PBlock *pb )
+{
+ LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_post_op\n",0,0,0);
+
+ cos_cache_change_notify(pb);
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_post_op\n",0,0,0);
+ return 0; /* always succeed */
+}
+
diff --git a/ldap/servers/plugins/cos/cos.def b/ldap/servers/plugins/cos/cos.def
new file mode 100644
index 00000000..484d9c6a
--- /dev/null
+++ b/ldap/servers/plugins/cos/cos.def
@@ -0,0 +1,11 @@
+; BEGIN COPYRIGHT BLOCK
+; Copyright 2001 Sun Microsystems, Inc.
+; Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+; All rights reserved.
+; END COPYRIGHT BLOCK
+;
+DESCRIPTION 'Netscape Directory Server 7 Class Of Service Plugin'
+EXPORTS
+ cos_init @2
+ plugin_init_debug_level @3
+ cos_version @4
diff --git a/ldap/servers/plugins/cos/cos_cache.c b/ldap/servers/plugins/cos/cos_cache.c
new file mode 100644
index 00000000..165f47d1
--- /dev/null
+++ b/ldap/servers/plugins/cos/cos_cache.c
@@ -0,0 +1,3566 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/*
+ The cos cache keeps in memory all of
+ the data related to cos. This allows
+ very fast lookups at the expense of RAM.
+ All meta data is indexed, allowing fast
+ binary search lookups.
+ The cache is not dynamic, in the sense
+ that it does not iteratively modify
+ itself as changes are made to the cos
+ meta-data. Rather, it is designed to
+ be fast to read, with non-locking
+ multiple thread access to the cache,
+ at the expense of modification speed.
+ This means that when changes do occur,
+ the cache must be rebuilt from scratch.
+ However, this is achieved in such a way,
+ so as to allow cache queries during the
+ building of the new cache - so once a
+ cache has been built, there is no down
+ time.
+ Of course, the configuration of the cos meta
+ data is likely to be a thing which does not
+ happen often. Any other use, is probably a
+ mis-use of the mechanism, and certainly will
+ suffer from performance problems.
+*/
+
+#include <stdio.h>
+#include <string.h>
+#include "portable.h"
+#include "slapi-plugin.h"
+#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */
+#include "dirver.h"
+
+/* this is naughty, but the api for backend state change is currently here */
+#include "slapi-private.h"
+
+/* include NSPR header files */
+#include "prthread.h"
+#include "prlock.h"
+#include "prerror.h"
+#include "prcvar.h"
+#include "prio.h"
+#include "vattr_spi.h"
+
+#include "cos_cache.h"
+
+#include "views.h"
+static void **views_api;
+
+/*** secret functions and structs in slapd ***/
+
+/*
+ these are required here because they are not available
+ in any public header. They must exactly match their
+ counterparts in the server or they will fail to work
+ correctly.
+*/
+
+/*** from slap.h ***/
+
+struct objclass {
+ char *oc_name; /* NAME */
+ char *oc_desc; /* DESC */
+ char *oc_oid; /* object identifier */
+ char *oc_superior; /* SUP -- XXXmcs: should be an array */
+ PRUint8 oc_kind; /* ABSTRACT/STRUCTURAL/AUXILIARY */
+ PRUint8 oc_flags; /* misc. flags, e.g., OBSOLETE */
+ char **oc_required;
+ char **oc_allowed;
+ char **oc_orig_required; /* MUST */
+ char **oc_orig_allowed; /* MAY */
+ char **oc_origin; /* X-ORIGIN extension */
+ struct objclass *oc_next;
+};
+
+/*** from proto-slap.h ***/
+
+int config_get_schemacheck();
+void oc_lock_read( void );
+void oc_unlock( void );
+struct objclass* g_get_global_oc_nolock();
+int slapd_log_error_proc( char *subsystem, char *fmt, ... );
+
+/*** from ldaplog.h ***/
+
+/* edited ldaplog.h for LDAPDebug()*/
+#ifndef _LDAPLOG_H
+#define _LDAPLOG_H
+
+/* defined in cos.c */
+void * cos_get_plugin_identity();
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define LDAP_DEBUG_TRACE 0x00001 /* 1 */
+#define LDAP_DEBUG_ANY 0x04000 /* 16384 */
+#define LDAP_DEBUG_PLUGIN 0x10000 /* 65536 */
+
+/* debugging stuff */
+# ifdef _WIN32
+ extern int *module_ldap_debug;
+# define LDAPDebug( level, fmt, arg1, arg2, arg3 ) \
+ { \
+ if ( *module_ldap_debug & level ) { \
+ slapd_log_error_proc( NULL, fmt, arg1, arg2, arg3 ); \
+ } \
+ }
+# else /* _WIN32 */
+ extern int slapd_ldap_debug;
+# define LDAPDebug( level, fmt, arg1, arg2, arg3 ) \
+ { \
+ if ( slapd_ldap_debug & level ) { \
+ slapd_log_error_proc( NULL, fmt, arg1, arg2, arg3 ); \
+ } \
+ }
+# endif /* Win32 */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _LDAP_H */
+
+/*** end secrets ***/
+
+#define COS_PLUGIN_SUBSYSTEM "cos-plugin" /* used for logging */
+
+#define COSTYPE_BADTYPE 0
+#define COSTYPE_CLASSIC 1
+#define COSTYPE_POINTER 2
+#define COSTYPE_INDIRECT 3
+#define COS_DEF_ERROR_NO_TEMPLATES -2
+
+/* the global plugin handle */
+static volatile vattr_sp_handle *vattr_handle = NULL;
+
+static cos_cache_notify_flag = 0;
+
+/* service definition cache structs */
+
+/* cosIndexedLinkedList: provides an object oriented type interface to
+ link lists where each element contains an index for the entire
+ list. All structures that contain this one must specify this one
+ as the first member otherwise the supporting functions will not work.
+
+ {PARPAR} The indexing ability is not currently used since the
+ fastest lookup is achieved via a cache level index of all attributes,
+ however this mechanism may prove useful in the future
+*/
+struct _cosIndexedLinkedList
+{
+ void *pNext;
+ void **index;
+};
+typedef struct _cosIndexedLinkedList cosIndexedLinkedList;
+
+struct _cosAttrValue
+{
+ cosIndexedLinkedList list;
+ char *val;
+};
+typedef struct _cosAttrValue cosAttrValue;
+
+struct _cosAttribute
+{
+ cosIndexedLinkedList list;
+ char *pAttrName;
+ cosAttrValue *pAttrValue;
+ cosAttrValue *pObjectclasses;
+ int attr_override;
+ int attr_operational;
+ int attr_operational_default;
+ int attr_cos_merge;
+ void *pParent;
+};
+typedef struct _cosAttribute cosAttributes;
+
+struct _cosTemplate
+{
+ cosIndexedLinkedList list;
+ cosAttrValue *pDn;
+ cosAttrValue *pObjectclasses;
+ cosAttributes *pAttrs;
+ char *cosGrade;
+ int template_default;
+ void *pParent;
+ unsigned long cosPriority;
+};
+
+typedef struct _cosTemplate cosTemplates;
+
+struct _cosDefinition
+{
+ cosIndexedLinkedList list;
+ int cosType;
+ cosAttrValue *pDn;
+ cosAttrValue *pCosTargetTree;
+ cosAttrValue *pCosTemplateDn;
+ cosAttrValue *pCosSpecifier;
+ cosAttrValue *pCosAttrs;
+ cosAttrValue *pCosOverrides;
+ cosAttrValue *pCosOperational;
+ cosAttrValue *pCosOpDefault;
+ cosAttrValue *pCosMerge;
+ cosTemplates *pCosTmps;
+};
+typedef struct _cosDefinition cosDefinitions;
+
+struct _cos_cache
+{
+ cosDefinitions *pDefs;
+ cosAttributes **ppAttrIndex;
+ int attrCount;
+ char **ppTemplateList;
+ int templateCount;
+ int refCount;
+ int vattr_cacheable;
+};
+typedef struct _cos_cache cosCache;
+
+/* cache manipulation function prototypes*/
+static cosCache *pCache; /* always the current global cache, only use getref to get */
+
+/* the place to start if you want a new cache */
+static int cos_cache_create();
+
+/* cache index related functions */
+static int cos_cache_index_all(cosCache *pCache);
+static int cos_cache_attr_compare(const void *e1, const void *e2);
+static int cos_cache_template_index_compare(const void *e1, const void *e2);
+static int cos_cache_string_compare(const void *e1, const void *e2);
+static int cos_cache_template_index_bsearch(const char *dn);
+static int cos_cache_attr_index_bsearch( const cosCache *pCache, const cosAttributes *key, int lower, int upper );
+
+/* the multi purpose list creation function, pass it something and it links it */
+static void cos_cache_add_ll_entry(void **attrval, void *theVal, int ( *compare )(const void *elem1, const void *elem2 ));
+
+/* cosAttrValue manipulation */
+static int cos_cache_add_attrval(cosAttrValue **attrval, char *val);
+static void cos_cache_del_attrval_list(cosAttrValue **pVal);
+static int cos_cache_attrval_count(cosAttrValue *pVal);
+static int cos_cache_attrval_exists(cosAttrValue *pAttrs, const char *val);
+
+/* cosAttributes manipulation */
+static int cos_cache_add_attr(cosAttributes **pAttrs, char *name, cosAttrValue *val);
+static void cos_cache_del_attr_list(cosAttributes **pAttrs);
+static int cos_cache_find_attr(cosCache *pCache, char *type);
+static int cos_cache_total_attr_count(cosCache *pCache);
+static int cos_cache_cos_2_slapi_valueset(cosAttributes *pAttr, Slapi_ValueSet **out_vs);
+static int cos_cache_cmp_attr(cosAttributes *pAttr, Slapi_Value *test_this, int *result);
+
+/* cosTemplates manipulation */
+static int cos_cache_add_dn_tmpls(char *dn, cosAttrValue *pCosSpecifier, cosAttrValue *pAttrs, cosTemplates **pTmpls);
+static int cos_cache_add_tmpl(cosTemplates **pTemplates, cosAttrValue *dn, cosAttrValue *objclasses, cosAttrValue *pCosSpecifier, cosAttributes *pAttrs,cosAttrValue *cosPriority);
+#if 0
+static int cos_cache_del_tmpl(cosTemplates *pTemplates, char *dn);
+#endif
+
+/* cosDefinitions manipulation */
+static int cos_cache_build_definition_list(cosDefinitions **pDefs, int *vattr_cacheable);
+static int cos_cache_add_dn_defs(char *dn, cosDefinitions **pDefs, int *vattr_cacheable);
+static int cos_cache_add_defn(cosDefinitions **pDefs, cosAttrValue **dn, int cosType, cosAttrValue **tree, cosAttrValue **tmpDn, cosAttrValue **spec, cosAttrValue **pAttrs, cosAttrValue **pOverrides, cosAttrValue **pOperational, cosAttrValue **pCosMerge, cosAttrValue **pCosOpDefault);
+static int cos_cache_entry_is_cos_related( Slapi_Entry *e);
+#if 0
+static int cos_cache_del_defn(cosDefinitions *pDefs, char *dn);
+#endif
+
+/* schema checking */
+static int cos_cache_schema_check(cosCache *pCache, int cache_attr_index, Slapi_Attr *pObjclasses);
+static int cos_cache_schema_build(cosCache *pCache);
+static void cos_cache_del_schema(cosCache *pCache);
+
+/* special cos scheme implimentations (special = other than cos classic) */
+static int cos_cache_follow_pointer( vattr_context *context, const char *dn, char *type, Slapi_ValueSet **out_vs, Slapi_Value *test_this, int *result, int flags );
+
+
+/* this dude is the thread function which performs dynamic config of the cache */
+static void cos_cache_wait_on_change(void *arg);
+
+/* this gets called when a backend changes state */
+void cos_cache_backend_state_change(void *handle, char *be_name,
+ int old_be_state, int new_be_state);
+
+/* operation callbacks for vattr service */
+static int cos_cache_vattr_get(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, char *type, Slapi_ValueSet** results,int *type_name_disposition, char** actual_type_name, int flags, int *free_flags, void *hint);
+static int cos_cache_vattr_compare(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, char *type, Slapi_Value *test_this, int* result, int flags, void *hint);
+static int cos_cache_vattr_types(vattr_sp_handle *handle,Slapi_Entry *e,vattr_type_list_context *type_context,int flags);
+static int cos_cache_query_attr(cos_cache *ptheCache, vattr_context *context, Slapi_Entry *e, char *type, Slapi_ValueSet **out_attr, Slapi_Value *test_this, int *result, int *ops);
+static void cos_cache_query_attr_free(struct berval ***vals); /* deprecated */
+
+
+/*
+ compares s2 to s1 starting from end of string until the beginning of either
+ matches result in the s2 value being clipped from s1 with a NULL char
+ and 1 being returned as opposed to 0
+*/
+static int cos_cache_backwards_stricmp_and_clip(char*s1,char*s2);
+
+/* module level thread control stuff */
+
+static PRThread *cos_tid = NULL;
+static int keeprunning = 0;
+static int started = 0;
+
+static Slapi_Mutex *cache_lock;
+static Slapi_Mutex *change_lock;
+static Slapi_Mutex *start_lock;
+static Slapi_Mutex *stop_lock;
+static Slapi_CondVar *something_changed = NULL;
+static Slapi_CondVar *start_cond = NULL;
+
+
+/*
+ cos_cache_init
+ --------------
+ starts up the thread which waits for changes and
+ fires off the cache re-creation when one is detected
+ also registers vattr callbacks
+*/
+int cos_cache_init()
+{
+ int ret = 0;
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_init\n",0,0,0);
+
+ slapi_vattrcache_cache_none();
+ cache_lock = slapi_new_mutex();
+ change_lock = slapi_new_mutex();
+ stop_lock = slapi_new_mutex();
+ something_changed = slapi_new_condvar(change_lock);
+ keeprunning =1;
+ start_lock = slapi_new_mutex();
+ start_cond = slapi_new_condvar(start_lock);
+ started = 0;
+
+ if ( stop_lock == NULL ||
+ change_lock == NULL ||
+ cache_lock == NULL ||
+ stop_lock == NULL ||
+ start_lock == NULL ||
+ start_cond == NULL ||
+ something_changed == NULL)
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, COS_PLUGIN_SUBSYSTEM,
+ "cos_cache_init: cannot create mutexes\n" );
+ ret = -1;
+ goto out;
+ }
+
+ /* grab the views interface */
+ if(slapi_apib_get_interface(Views_v1_0_GUID, &views_api))
+ {
+ /* lets be tolerant if views is disabled */
+ views_api = 0;
+ }
+
+ if (slapi_vattrspi_register((vattr_sp_handle **)&vattr_handle,
+ cos_cache_vattr_get,
+ cos_cache_vattr_compare,
+ cos_cache_vattr_types) != 0)
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, COS_PLUGIN_SUBSYSTEM,
+ "cos_cache_init: cannot register as service provider\n" );
+ ret = -1;
+ goto out;
+ }
+
+ if ((cos_tid = PR_CreateThread (PR_USER_THREAD,
+ cos_cache_wait_on_change,
+ NULL,
+ PR_PRIORITY_NORMAL,
+ PR_GLOBAL_THREAD,
+ PR_UNJOINABLE_THREAD,
+ SLAPD_DEFAULT_THREAD_STACKSIZE)) == NULL )
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, COS_PLUGIN_SUBSYSTEM,
+ "cos_cache_init: PR_CreateThread failed\n" );
+ ret = -1;
+ goto out;
+ }
+
+ /* wait for that thread to get started */
+ if (ret == 0) {
+ slapi_lock_mutex(start_lock);
+ while (!started) {
+ while (slapi_wait_condvar(start_cond, NULL) == 0);
+ }
+ slapi_unlock_mutex(start_lock);
+ }
+
+
+out:
+ LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_init\n",0,0,0);
+ return ret;
+}
+
+/*
+ cos_cache_wait_on_change
+ ------------------------
+ sit around waiting on a notification that something has
+ changed, then fires off the cache re-creation
+
+ The way this stuff is written, we can look for the
+ template for a definiton, before the template has been added--I think
+ that's OK--we'll see it when it arrives--get this error message:
+ "skipping cos definition cn=cosPointerGenerateSt,ou=People,o=cosAll--no templates found"
+
+*/
+static void cos_cache_wait_on_change(void *arg)
+{
+ LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_wait_on_change thread\n",0,0,0);
+
+ slapi_lock_mutex(stop_lock);
+ slapi_lock_mutex(change_lock);
+
+ /* first register our backend state change func (we'll use func pointer as handle) */
+ slapi_register_backend_state_change((void *)cos_cache_backend_state_change, cos_cache_backend_state_change);
+
+ pCache = 0;
+
+ /* create initial cache */
+ cos_cache_create();
+
+ slapi_lock_mutex(start_lock);
+ started = 1;
+ slapi_notify_condvar(start_cond, 1);
+ slapi_unlock_mutex(start_lock);
+
+ while(keeprunning)
+ {
+ slapi_unlock_mutex(change_lock);
+ slapi_lock_mutex(change_lock);
+ if ( !cos_cache_notify_flag && keeprunning) {
+ /*
+ * Nothing to do right now, so go to sleep--as
+ * we have the mutex, we are sure to wait before any modify
+ * thread notifies our condvar, and so we will not miss any
+ * notifications, including the shutdown notification.
+ */
+ slapi_wait_condvar( something_changed, NULL );
+ } else {
+ /* Something to do...do it below */
+ }
+ /*
+ * We're here because:
+ * 1. we were asleep and got a signal, on our condvar OR
+ * 2. we were about to wait on the condvar and noticed that a modfiy
+ * thread
+ * had passed, setting the cos_cache_notify_flag and notifying us--
+ * we did not wait in that case as we would have missed the notify
+ * (notify when noone is waiting == no-op).
+ * before we go running off doing lots of stuff lets check if we should stop
+ */
+ if(keeprunning) {
+ cos_cache_create();
+ }
+ cos_cache_notify_flag = 0; /* Dealt with it */
+ }/* while */
+
+ /* shut down the cache */
+ slapi_unlock_mutex(change_lock);
+ slapi_unlock_mutex(stop_lock);
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_wait_on_change thread exit\n",0,0,0);
+}
+
+/*
+ cos_cache_create
+ ---------------------
+ Walks the definitions in the DIT and creates the cache.
+ Once created, it swaps the new cache for the old one,
+ releasing its refcount to the old cache and allowing it
+ to be destroyed.
+*/
+static int cos_cache_create()
+{
+ int ret = -1;
+ cosCache *pNewCache;
+ static int firstTime = 1;
+ int cache_built = 0;
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_create\n",0,0,0);
+
+ pNewCache = (cosCache*)slapi_ch_malloc(sizeof(cosCache));
+ if(pNewCache)
+ {
+ pNewCache->pDefs = 0;
+ pNewCache->refCount = 1; /* 1 is for us */
+ pNewCache->vattr_cacheable = 0; /* default is not cacheable */
+
+ ret = cos_cache_build_definition_list(&(pNewCache->pDefs), &(pNewCache->vattr_cacheable));
+ if(!ret)
+ {
+ /* OK, we have a cache, lets add indexing for
+ that faster than slow feeling */
+
+ ret = cos_cache_index_all(pNewCache);
+ if(ret == 0)
+ {
+ /* right, indexed cache, lets do our duty for the schema */
+
+ ret = cos_cache_schema_build(pNewCache);
+ if(ret == 0)
+ {
+ /* now to swap the new cache for the old cache */
+ cosCache *pOldCache;
+
+ slapi_lock_mutex(cache_lock);
+
+ /* turn off caching until the old cache is done */
+ if(pCache)
+ {
+ slapi_vattrcache_cache_none();
+
+ /*
+ * be sure not to uncache other stuff
+ * like roles if there is no change in
+ * state
+ */
+ if(pCache->vattr_cacheable)
+ slapi_entrycache_vattrcache_watermark_invalidate();
+ }
+ else
+ {
+ if(pNewCache && pNewCache->vattr_cacheable)
+ {
+ slapi_vattrcache_cache_all();
+ }
+ }
+
+ pOldCache = pCache;
+ pCache = pNewCache;
+
+ slapi_unlock_mutex(cache_lock);
+
+ if(pOldCache)
+ cos_cache_release(pOldCache);
+
+ cache_built = 1;
+ }
+ else
+ {
+ /* we should not go on without proper schema checking */
+ cos_cache_release(pNewCache);
+ LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_create: failed to cache the schema\n",0,0,0);
+ }
+ }
+ else
+ {
+ /* currently we cannot go on without the indexes */
+ cos_cache_release(pNewCache);
+ LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_create: failed to index cache\n",0,0,0);
+ }
+ }
+ else
+ {
+ if(firstTime)
+ {
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "cos_cache_create: cos disabled\n",0,0,0);
+ firstTime = 0;
+ }
+
+ slapi_ch_free((void**)&pNewCache);
+ }
+ }
+ else
+ LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_create: memory allocation failure\n",0,0,0);
+
+
+ /* make sure we have a new cache */
+ if(!cache_built)
+ {
+ /* we do not have a new cache, must make sure the old cache is destroyed */
+
+ cosCache *pOldCache;
+
+ slapi_lock_mutex(cache_lock);
+
+ slapi_vattrcache_cache_none();
+
+ /*
+ * be sure not to uncache other stuff
+ * like roles if there is no change in
+ * state
+ */
+ if(pCache && pCache->vattr_cacheable)
+ slapi_entrycache_vattrcache_watermark_invalidate();
+
+ pOldCache = pCache;
+ pCache = NULL;
+
+ slapi_unlock_mutex(cache_lock);
+
+ if(pOldCache)
+ cos_cache_release(pOldCache); /* release our reference to the old cache */
+
+ }
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_create\n",0,0,0);
+ return ret;
+}
+
+
+/*
+ cos_cache_build_definition_list
+ -------------------------------
+ builds the list of cos definitions by searching for them throughout the DIT
+*/
+static int cos_cache_build_definition_list(cosDefinitions **pDefs, int *vattr_cacheable)
+{
+ int ret = 0;
+ Slapi_PBlock *pSuffixSearch = 0;
+ Slapi_Entry **pSuffixList = 0;
+ Slapi_Attr *suffixAttr;
+ struct berval **suffixVals;
+ char *attrType = 0;
+ char *attrs[2];
+ int suffixIndex = 0;
+ int valIndex = 0;
+ int cos_def_available = 0;
+ static int firstTime = 1;
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_build_definition_list\n",0,0,0);
+
+ /*
+ the class of service definitions may be anywhere in the DIT,
+ so our first task is to find them.
+ */
+
+ attrs[0] = "namingcontexts";
+ attrs[1] = 0;
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "cos: Building class of service cache after status change.\n",0,0,0);
+
+ /*
+ * XXXrbyrne: this looks really ineficient--should be using
+ * slapi_get_next_suffix(), rather than searching for namingcontexts.
+ */
+
+ pSuffixSearch = slapi_search_internal("",LDAP_SCOPE_BASE,"(objectclass=*)",NULL,attrs,0);
+ if(pSuffixSearch)
+ slapi_pblock_get( pSuffixSearch, SLAPI_PLUGIN_INTOP_RESULT, &ret);
+
+ if(pSuffixSearch && ret == LDAP_SUCCESS)
+ {
+ /* iterate through the suffixes and search for cos definitions */
+ slapi_pblock_get( pSuffixSearch, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &pSuffixList);
+ if(pSuffixList)
+ {
+ while(pSuffixList[suffixIndex])
+ {
+ if(!slapi_entry_first_attr(pSuffixList[suffixIndex], &suffixAttr))
+ {
+ do
+ {
+ attrType = 0;
+ slapi_attr_get_type(suffixAttr, &attrType);
+ if(attrType && !slapi_utf8casecmp((unsigned char*)attrType, (unsigned char*)"namingcontexts"))
+ {
+ if(!slapi_attr_get_bervals_copy(suffixAttr, &suffixVals))
+ {
+ valIndex = 0;
+
+ if(suffixVals)
+ {
+ while(suffixVals[valIndex])
+ {
+ /* here's a suffix, lets search it... */
+ if(suffixVals[valIndex]->bv_val)
+ if(!cos_cache_add_dn_defs(suffixVals[valIndex]->bv_val ,pDefs, vattr_cacheable))
+ cos_def_available = 1;
+
+ valIndex++;
+ }
+
+
+ ber_bvecfree( suffixVals );
+ suffixVals = NULL;
+ }
+ }
+ }
+
+ } while(!slapi_entry_next_attr(pSuffixList[suffixIndex], suffixAttr, &suffixAttr));
+ }
+ suffixIndex++;
+ }
+ }
+ }
+ else
+ {
+ LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_build_definition_list: failed to find suffixes\n",0,0,0);
+ ret = -1;
+ }
+
+ if(cos_def_available == 0)
+ {
+ if(firstTime)
+ {
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "cos_cache_build_definition_list: Found no cos definitions, cos disabled while waiting for updates\n",0,0,0);
+ firstTime = 0;
+ }
+
+ ret = -1;
+ }
+ else
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "cos: Class of service cache built.\n",0,0,0);
+
+ /* clean up */
+ if(pSuffixSearch)
+ {
+ slapi_free_search_results_internal(pSuffixSearch);
+ slapi_pblock_destroy(pSuffixSearch);
+ }
+
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_build_definition_list\n",0,0,0);
+ return ret;
+}
+
+
+/* struct to support search callback API */
+struct dn_defs_info {
+ cosDefinitions **pDefs;
+ int vattr_cacheable;
+ int ret;
+};
+
+/*
+ * Currently, always returns 0 to continue the search for definitions, even
+ * if a particular attempt to add a definition fails: info.ret gets set to
+ * zero only if we succed to add a def.
+*/
+static int cos_dn_defs_cb (Slapi_Entry* e, void *callback_data) {
+ struct dn_defs_info *info;
+ cosAttrValue **pSneakyVal = 0;
+ cosAttrValue *pObjectclass = 0;
+ cosAttrValue *pCosTargetTree = 0;
+ cosAttrValue *pCosTemplateDn = 0;
+ cosAttrValue *pCosSpecifier = 0;
+ cosAttrValue *pCosAttribute = 0;
+ cosAttrValue *pCosOverrides = 0;
+ cosAttrValue *pCosOperational = 0;
+ cosAttrValue *pCosOpDefault = 0;
+ cosAttrValue *pCosMerge = 0;
+ cosAttrValue *pDn = 0;
+ struct berval **dnVals;
+ int cosType = 0;
+ int valIndex = 0;
+ Slapi_Attr *dnAttr;
+ char *attrType = 0;
+ char *attrs[7];
+
+ attrs[0] = "objectclass";
+ attrs[1] = "cosTargetTree";
+ attrs[2] = "cosTemplateDn";
+ attrs[3] = "cosSpecifier";
+ attrs[4] = "cosAttribute";
+ attrs[5] = 0;
+ info=(struct dn_defs_info *)callback_data;
+
+
+ /* assume cacheable */
+ info->vattr_cacheable = -1;
+
+ cos_cache_add_attrval(&pDn, slapi_entry_get_dn(e));
+ if(!slapi_entry_first_attr(e, &dnAttr))
+ {
+ do
+ {
+ attrType = 0;
+ /* we need to fill in the details of the definition now */
+ slapi_attr_get_type(dnAttr, &attrType);
+ if(attrType)
+ {
+ pSneakyVal = 0;
+ if(!slapi_utf8casecmp((unsigned char*)attrType, (unsigned char*)"objectclass"))
+ pSneakyVal = &pObjectclass;
+ else if(!slapi_utf8casecmp((unsigned char*)attrType, (unsigned char*)"cosTargetTree"))
+ pSneakyVal = &pCosTargetTree;
+ else if(!slapi_utf8casecmp((unsigned char*)attrType, (unsigned char*)"cosTemplateDn"))
+ pSneakyVal = &pCosTemplateDn;
+ else if(!slapi_utf8casecmp((unsigned char*)attrType, (unsigned char*)"cosSpecifier"))
+ pSneakyVal = &pCosSpecifier;
+ else if(!slapi_utf8casecmp((unsigned char*)attrType, (unsigned char*)"cosAttribute"))
+ pSneakyVal = &pCosAttribute;
+ else if(!slapi_utf8casecmp((unsigned char*)attrType, (unsigned char*)"cosIndirectSpecifier"))
+ pSneakyVal = &pCosSpecifier;
+ if(pSneakyVal)
+ {
+ /* It's a type we're interested in */
+ if(!slapi_attr_get_bervals_copy(dnAttr, &dnVals))
+ {
+ valIndex = 0;
+ if(dnVals)
+ {
+ while(dnVals[valIndex])
+ {
+ if(dnVals[valIndex]->bv_val)
+ {
+ /*
+ parse any overide or default values
+ and deal with them
+ */
+ if(pSneakyVal == &pCosAttribute)
+ {
+ cosAttrValue *pTmpTargetTree = 0;
+ int qualifier_hit = 0;
+ int op_qualifier_hit = 0;
+ int merge_schemes_qualifier_hit = 0;
+ int override_qualifier_hit =0;
+ int default_qualifier_hit = 0;
+ int operational_default_qualifier_hit = 0;
+ do
+ {
+ qualifier_hit = 0;
+
+ if(cos_cache_backwards_stricmp_and_clip(dnVals[valIndex]->bv_val, " operational"))
+ {
+ /* matched */
+ op_qualifier_hit = 1;
+ qualifier_hit = 1;
+ }
+
+ if(cos_cache_backwards_stricmp_and_clip(dnVals[valIndex]->bv_val, " merge-schemes"))
+ {
+ /* matched */
+ merge_schemes_qualifier_hit = 1;
+ qualifier_hit = 1;
+ }
+
+ if(cos_cache_backwards_stricmp_and_clip(dnVals[valIndex]->bv_val, " override"))
+ {
+ /* matched */
+ override_qualifier_hit = 1;
+ qualifier_hit = 1;
+ }
+
+ if(cos_cache_backwards_stricmp_and_clip(dnVals[valIndex]->bv_val, " default")) {
+ default_qualifier_hit = 1;
+ qualifier_hit = 1;
+ }
+
+ if(cos_cache_backwards_stricmp_and_clip(dnVals[valIndex]->bv_val, " operational-default")) {
+ operational_default_qualifier_hit = 1;
+ qualifier_hit = 1;
+ }
+ }
+ while(qualifier_hit == 1);
+
+ /*
+ * At this point, dnVals[valIndex]->bv_val
+ * is the value of cosAttribute, stripped of
+ * any qualifiers, so add this pure attribute type to
+ * the appropriate lists.
+ */
+
+ if ( op_qualifier_hit ) {
+ cos_cache_add_attrval(&pCosOperational,
+ dnVals[valIndex]->bv_val);
+ }
+ if ( merge_schemes_qualifier_hit ) {
+ cos_cache_add_attrval(&pCosMerge,
+ dnVals[valIndex]->bv_val);
+ }
+ if ( override_qualifier_hit ) {
+ cos_cache_add_attrval(&pCosOverrides,
+ dnVals[valIndex]->bv_val);
+ }
+ if ( default_qualifier_hit ) {
+ /* attr is added below in pSneakyVal, in any case */
+ }
+
+ if ( operational_default_qualifier_hit ) {
+ cos_cache_add_attrval(&pCosOpDefault,
+ dnVals[valIndex]->bv_val);
+ }
+
+ if(!pCosTargetTree)
+ {
+ /* get the parent of the definition */
+
+ char *parent = slapi_dn_parent(pDn->val);
+ slapi_dn_normalize( parent );
+
+ cos_cache_add_attrval(&pCosTargetTree, parent);
+ if(!pCosTemplateDn)
+ cos_cache_add_attrval(&pCosTemplateDn, parent);
+
+ slapi_ch_free((void**)&parent);
+ }
+
+ pTmpTargetTree = pCosTargetTree;
+
+ slapi_vattrspi_regattr((vattr_sp_handle *)vattr_handle, dnVals[valIndex]->bv_val, NULL, NULL);
+ } /* if(attrType is cosAttribute) */
+
+ /*
+ * Add the attributetype to the appropriate
+ * list.
+ */
+ cos_cache_add_attrval(pSneakyVal,
+ dnVals[valIndex]->bv_val);
+ }/*if(dnVals[valIndex]->bv_val)*/
+
+ valIndex++;
+ }/* while(dnVals[valIndex]) */
+
+ ber_bvecfree( dnVals );
+ dnVals = NULL;
+ }/*if(dnVals)*/
+ }
+ }/*if(pSneakyVal)*/
+ }/*if(attrType)*/
+
+ } while(!slapi_entry_next_attr(e, dnAttr, &dnAttr));
+
+ /*
+ determine the type of class of service scheme
+ */
+
+ if(pObjectclass)
+ {
+ if(cos_cache_attrval_exists(pObjectclass, "cosDefinition"))
+ {
+ cosType = COSTYPE_CLASSIC;
+ }
+ else if(cos_cache_attrval_exists(pObjectclass, "cosClassicDefinition"))
+ {
+ cosType = COSTYPE_CLASSIC;
+
+ }
+ else if(cos_cache_attrval_exists(pObjectclass, "cosPointerDefinition"))
+ {
+ cosType = COSTYPE_POINTER;
+
+ }
+ else if(cos_cache_attrval_exists(pObjectclass, "cosIndirectDefinition"))
+ {
+ cosType = COSTYPE_INDIRECT;
+
+ }
+ else
+ cosType = COSTYPE_BADTYPE;
+ }
+
+ /*
+ we should now have a full definition,
+ do some sanity checks because we don't
+ want bogus entries in the cache
+ then ship it
+ */
+
+ /* these must exist */
+ if( pDn &&
+ pObjectclass &&
+
+ (
+ (cosType == COSTYPE_CLASSIC &&
+ pCosTemplateDn &&
+ pCosSpecifier &&
+ pCosAttribute )
+ ||
+ (cosType == COSTYPE_POINTER &&
+ pCosTemplateDn &&
+ pCosAttribute )
+ ||
+ (cosType == COSTYPE_INDIRECT &&
+ pCosSpecifier &&
+ pCosAttribute )
+ )
+ )
+ {
+ int rc = 0;
+ /*
+ we'll leave the referential integrity stuff
+ up to the referint plug-in and assume all
+ is good - if it's not then we just have a
+ useless definition and we'll nag copiously later.
+ */
+ char *pTmpDn = slapi_ch_strdup(pDn->val); /* because dn gets hosed on error */
+ char ebuf[ BUFSIZ ];
+
+ if(!(rc = cos_cache_add_defn(info->pDefs, &pDn, cosType,
+ &pCosTargetTree, &pCosTemplateDn,
+ &pCosSpecifier, &pCosAttribute,
+ &pCosOverrides, &pCosOperational,
+ &pCosMerge, &pCosOpDefault))) {
+ info->ret = 0; /* we have succeeded to add the defn*/
+ } else {
+ /*
+ * Failed but we will continue the search for other defs
+ * Don't reset info->ret....it keeps track of any success
+ */
+ if ( rc == COS_DEF_ERROR_NO_TEMPLATES) {
+ LDAPDebug(LDAP_DEBUG_ANY, "skipping cos definition %s"
+ "--no templates found\n",
+ escape_string(pTmpDn, ebuf),0,0);
+ } else {
+ LDAPDebug(LDAP_DEBUG_ANY, "skipping cos definition %s\n"
+ ,escape_string(pTmpDn, ebuf),0,0);
+ }
+ }
+
+ slapi_ch_free_string(&pTmpDn);
+ }
+ else
+ {
+ /*
+ this definition is brain dead - bail
+ if we have a dn use it to report, if not then *really* bad
+ things are going on
+ */
+ if(pDn)
+ {
+ LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_add_dn_defs: incomplete cos definition detected in %s, discarding from cache.\n",pDn->val,0,0);
+ }
+ else
+ LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_add_dn_defs: incomplete cos definition detected, no DN to report, discarding from cache.\n",0,0,0);
+
+ if(pCosTargetTree)
+ cos_cache_del_attrval_list(&pCosTargetTree);
+ if(pCosTemplateDn)
+ cos_cache_del_attrval_list(&pCosTemplateDn);
+ if(pCosSpecifier)
+ cos_cache_del_attrval_list(&pCosSpecifier);
+ if(pCosAttribute)
+ cos_cache_del_attrval_list(&pCosAttribute);
+ if(pDn)
+ cos_cache_del_attrval_list(&pDn);
+ }
+ }/*if(!slapi_entry_first_attr(e, &dnAttr))*/
+ /* we don't keep the objectclasses, so lets free them */
+ if(pObjectclass) {
+ cos_cache_del_attrval_list(&pObjectclass);
+ }
+ /* This particular definition may not have yielded anything
+ * worth caching (eg. no template was found for it) but
+ * that should not cause us to abort the search for other more well behaved
+ * definitions.
+ * return info->ret;
+ */
+ return (0);
+
+}
+
+/*
+ cos_cache_add_dn_defs
+ -------------------------
+ takes a dn as argument and searches the dn for cos definitions,
+ adding any found to the definition list. Change to use search callback API.
+
+ Returns: 0: found at least one definition entry that got added to the
+ cache successfully.
+ non-zero: added no cos defs to the cache.
+*/
+
+#define DN_DEF_FILTER "(&(|(objectclass=cosSuperDefinition)(objectclass=cosDefinition))(objectclass=ldapsubentry))"
+
+static int cos_cache_add_dn_defs(char *dn, cosDefinitions **pDefs, int *vattr_cacheable)
+{
+ Slapi_PBlock *pDnSearch = 0;
+ struct dn_defs_info info;
+ pDnSearch = slapi_pblock_new();
+ if (pDnSearch) {
+ info.ret=-1; /* assume no good defs */
+ info.pDefs=pDefs;
+ info.vattr_cacheable = 0; /* assume not cacheable */
+ slapi_search_internal_set_pb(pDnSearch, dn, LDAP_SCOPE_SUBTREE,
+ DN_DEF_FILTER,NULL,0,
+ NULL,NULL,cos_get_plugin_identity(),0);
+ slapi_search_internal_callback_pb(pDnSearch,
+ &info /* callback_data */,
+ NULL/* result_callback */,
+ cos_dn_defs_cb,
+ NULL /* referral_callback */);
+ slapi_pblock_destroy (pDnSearch);
+ }
+
+ *vattr_cacheable = info.vattr_cacheable;
+
+ return info.ret;
+}
+
+
+
+/* struct to support call back API */
+
+struct tmpl_info {
+ cosAttrValue *pCosSpecifier;
+ cosAttrValue *pAttrs;
+ cosTemplates **pTmpls;
+ int ret;
+};
+
+
+/*
+ * Currently, always returns 0 to continue the search for templates, even
+ * if a particular attempt to add a template fails: info.ret gets set to
+ * zero only if we succed to add at least one tmpl.
+*/
+static int cos_dn_tmpl_entries_cb (Slapi_Entry* e, void *callback_data) {
+ cosAttrValue *pDn = 0;
+ cosAttrValue *pCosPriority = 0;
+ cosAttributes *pAttributes = 0;
+ cosAttrValue *pObjectclass = 0;
+ cosAttrValue *pCosAttribute = 0;
+ Slapi_Attr *dnAttr;
+ struct berval **dnVals;
+ int itsAnAttr = 0;
+ int valIndex = 0;
+ cosAttrValue **pSneakyVal = 0;
+ char *attrType = 0;
+ struct tmpl_info *info;
+ info = (struct tmpl_info *)callback_data;
+
+ pDn = 0;
+ cos_cache_add_attrval(&pDn, slapi_entry_get_dn(e));
+ pAttributes = 0;
+ pObjectclass = 0;
+ pCosPriority = 0;
+
+ if(!slapi_entry_first_attr(e, &dnAttr))
+ {
+ int attrs_present = 0;
+
+ do
+ {
+ attrType = 0;
+ pCosAttribute = 0;
+
+ /* we need to fill in the details of the template now */
+ slapi_attr_get_type(dnAttr, &attrType);
+
+ if(attrType)
+ {
+ itsAnAttr = 0;
+ pSneakyVal = 0;
+
+ if(!slapi_utf8casecmp((unsigned char*)attrType,
+ (unsigned char*)"objectclass"))
+ pSneakyVal = &pObjectclass;
+
+ if(!slapi_utf8casecmp((unsigned char*)attrType,
+ (unsigned char*)"cosPriority"))
+ pSneakyVal = &pCosPriority;
+
+ if(pSneakyVal == NULL)
+ {
+ /* look for the atrribute in the dynamic attributes */
+ if(cos_cache_attrval_exists(info->pAttrs, attrType))
+ {
+ pSneakyVal = &pCosAttribute;
+ itsAnAttr = 1;
+ attrs_present = 1;
+ }
+ }
+
+ if(pSneakyVal)
+ {
+ if(!slapi_attr_get_bervals_copy(dnAttr, &dnVals))
+ {
+ valIndex = 0;
+
+ if(dnVals)
+ {
+ while(dnVals[valIndex])
+ {
+ if(dnVals[valIndex]->bv_val)
+ cos_cache_add_attrval(pSneakyVal,
+ dnVals[valIndex]->bv_val);
+
+ valIndex++;
+ }
+
+ if(itsAnAttr)
+ {
+ /* got all vals, add this attribute to the attribute list */
+ cos_cache_add_attr(&pAttributes, attrType,
+ *pSneakyVal);
+ }
+
+ ber_bvecfree( dnVals );
+ dnVals = NULL;
+ }
+ }
+ }
+ }
+
+ } while(!slapi_entry_next_attr(e, dnAttr, &dnAttr));
+
+ /*
+ we should now have a full template,
+ do some sanity checks because we don't
+ want bogus entries in the cache if we can help it
+ - then ship it
+ */
+
+ /* these must exist */
+ if(
+ attrs_present &&
+ pObjectclass &&
+ pAttributes &&
+ pDn
+ )
+ {
+ /*
+ we'll leave the referential integrity stuff
+ up to the referint plug-in if set up and assume all
+ is good - if it's not then we just have a
+ useless definition and we'll nag copiously later.
+ */
+
+ if(!cos_cache_add_tmpl(info->pTmpls, pDn, pObjectclass,
+ info->pCosSpecifier, pAttributes,pCosPriority)){
+ info->ret = 0; /* we have succeed to add the tmpl */
+ } else {
+ /* Don't reset info->ret....it keeps track of any success */
+ LDAPDebug(LDAP_DEBUG_ANY, "cos_cache_add_dn_tmpls:"
+ "could not cache cos template %s\n",pDn,0,0);
+ }
+ }
+ else
+ {
+ /*
+ this template is brain dead - bail
+ if we have a dn use it to report, if not then *really* bad
+ things are going on
+ - of course it might not be a template, so lets
+ not tell the world unless the world wants to know,
+ we'll make it a plugin level message
+ */
+ if(pDn)
+ {
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "cos_cache_add_dn_tmpls: incomplete cos template detected in %s, discarding from cache.\n",pDn->val,0,0);
+ }
+ else
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "cos_cache_add_dn_tmpls: incomplete cos template detected, no DN to report, discarding from cache.\n",0,0,0);
+
+ if(pObjectclass)
+ cos_cache_del_attrval_list(&pObjectclass);
+ if(pCosAttribute)
+ cos_cache_del_attrval_list(&pCosAttribute);
+ if(pDn)
+ cos_cache_del_attrval_list(&pDn);
+ if(pAttributes)
+ cos_cache_del_attr_list(&pAttributes);
+ }
+ }
+ /*
+ * Always contine the search even if a particular attempt
+ * to add a template failed.
+ */
+ return 0;
+}
+
+/*
+ cos_cache_add_dn_tmpls
+ -------------------------
+ takes a dn as argument and searches the dn for cos templates,
+ adding any found to the template list
+ This is the new version using call back search API
+
+ Returns: zero for success--found at least one good tmpl for this def.
+ non-zero: failed to add any templs for this def.
+*/
+
+#define TMPL_FILTER "(&(objectclass=costemplate)(|(objectclass=costemplate)(objectclass=ldapsubentry)))"
+
+static int cos_cache_add_dn_tmpls(char *dn, cosAttrValue *pCosSpecifier, cosAttrValue *pAttrs, cosTemplates **pTmpls)
+{
+ void *plugin_id;
+ int scope;
+ struct tmpl_info info;
+ Slapi_PBlock *pDnSearch = 0;
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_add_dn_tmpls\n",0,0,0);
+
+ /* no cos specifier means this is an indirect scheme */
+ if(pCosSpecifier)
+ scope = LDAP_SCOPE_ONELEVEL;
+ else
+ scope = LDAP_SCOPE_BASE;
+
+ /* Use new internal operation API */
+ pDnSearch = slapi_pblock_new();
+ plugin_id=cos_get_plugin_identity();
+ if (pDnSearch) {
+ info.pAttrs=pAttrs;
+ info.pTmpls=pTmpls;
+ info.pCosSpecifier=pCosSpecifier;
+ info.ret=-1; /* assume no good tmpls */
+ slapi_search_internal_set_pb(pDnSearch, dn, scope,
+ TMPL_FILTER,NULL,0,
+ NULL,NULL,plugin_id,0);
+ slapi_search_internal_callback_pb(pDnSearch,
+ &info /* callback_data */,
+ NULL/* result_callback */,
+ cos_dn_tmpl_entries_cb,
+ NULL /* referral_callback */);
+ slapi_pblock_destroy (pDnSearch);
+ }
+ /*
+ * info.ret comes out zero only if we succeed to add at least one
+ * tmpl to the cache.
+ */
+ return (info.ret);
+}
+
+/*
+ cos_cache_add_defn
+ ------------------
+ Add a cos definition to the list and create the template
+ cache for this definition
+ returns: 0: successfully added the definition to the cache
+ non-zero: failed to add the definition to the cache (eg. because
+ there was no template for it.)
+ ret==COS_DEF_ERROR_NO_TEMPLATES then no templs were found
+ for thsi def. We make a special case of this and pass the
+ back the error so we can roll two messages into one--this
+ is to reduce the number of error messages at cos definiton
+ load time--it is common to see the defs before the tmpls
+ arrive.
+
+*/
+static int cos_cache_add_defn(
+ cosDefinitions **pDefs,
+ cosAttrValue **dn,
+ int cosType,
+ cosAttrValue **tree,
+ cosAttrValue **tmpDn,
+ cosAttrValue **spec,
+ cosAttrValue **pAttrs,
+ cosAttrValue **pOverrides,
+ cosAttrValue **pOperational,
+ cosAttrValue **pCosMerge,
+ cosAttrValue **pCosOpDefault
+ )
+{
+ int ret = 0;
+ int tmplCount = 0;
+ cosDefinitions *theDef = 0;
+ cosAttrValue *pTmpTmplDn = *tmpDn;
+ cosAttrValue *pDummyAttrVal = 0;
+ cosAttrValue *pAttrsIter = 0;
+ cosAttributes *pDummyAttributes = 0;
+ cosAttrValue *pSpecsIter = *spec;
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_add_defn\n",0,0,0);
+
+ /* we don't want cosspecifiers that can be supplied by the same scheme */
+ while( pSpecsIter )
+ {
+ if( cos_cache_attrval_exists(*pAttrs, pSpecsIter->val ) )
+ {
+ /* no, this is not sane, lets reject the whole darn scheme in disgust */
+ LDAPDebug( LDAP_DEBUG_ANY, "cos definition %s supplies its own cosspecifier, rejecting scheme\n",(*dn)->val,0,0);
+ ret = -1;
+ }
+
+ pSpecsIter = pSpecsIter->list.pNext;
+ }
+
+ /* create the definition */
+ if(0 == ret)
+ {
+ theDef = (cosDefinitions*) slapi_ch_malloc(sizeof(cosDefinitions));
+ if(theDef)
+ {
+ theDef->pCosTmps = NULL;
+
+ /* process each template in turn */
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "Processing cosDefinition %s\n",(*dn)->val,0,0);
+
+ while(pTmpTmplDn && cosType != COSTYPE_INDIRECT)
+ {
+ /* create the template */
+ if(!cos_cache_add_dn_tmpls(pTmpTmplDn->val, *spec, *pAttrs, &(theDef->pCosTmps)))
+ tmplCount++;
+
+ pTmpTmplDn = pTmpTmplDn->list.pNext;
+ }
+
+ if(tmplCount == 0 && cosType != COSTYPE_INDIRECT)
+ {
+ /* without our golden templates we are nothing
+ LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_add_defn:"
+ "no templates for cos definition at %s.\n",(*dn)->val,0,0);*/
+ ret = COS_DEF_ERROR_NO_TEMPLATES;
+ }
+ else
+ {
+ if(cosType == COSTYPE_INDIRECT)
+ {
+ /*
+ Indirect cos schemes have no templates,
+ however, in order to take advantage of existing code
+ which is optimized to do a binary search on attributes
+ which are found through their templates, we add a dummy
+ template and dummy attributes. The value of the attributes
+ will be ignored when later assessing a query.
+ */
+ pAttrsIter = *pAttrs;
+
+ while(pAttrsIter)
+ {
+ pDummyAttrVal = NULL;
+ cos_cache_add_attrval(&pDummyAttrVal, "not used");
+ cos_cache_add_attr(&pDummyAttributes, pAttrsIter->val, pDummyAttrVal);
+
+ pAttrsIter = pAttrsIter->list.pNext;
+ }
+
+ cos_cache_add_attrval(tmpDn, "cn=dummy,");
+
+ cos_cache_add_tmpl(&(theDef->pCosTmps), *tmpDn, NULL, *spec, pDummyAttributes,NULL);
+ *tmpDn = 0;
+ }
+
+ theDef->pDn = *dn;
+ theDef->cosType = cosType;
+ theDef->pCosTargetTree = *tree;
+ theDef->pCosTemplateDn = *tmpDn;
+ theDef->pCosSpecifier = *spec;
+ theDef->pCosAttrs = *pAttrs;
+ theDef->pCosOverrides = *pOverrides;
+ theDef->pCosOperational = *pOperational;
+ theDef->pCosMerge = *pCosMerge;
+ theDef->pCosOpDefault = *pCosOpDefault;
+
+ cos_cache_add_ll_entry((void**)pDefs, theDef, NULL);
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "Added cosDefinition %s\n",(*dn)->val,0,0);
+ }
+ }
+ else
+ {
+ ret = -1;
+ }
+ }
+
+ if(ret == -1)
+ {
+ if(dn)
+ cos_cache_del_attrval_list(dn);
+ if(tree)
+ cos_cache_del_attrval_list(tree);
+ if(tmpDn)
+ cos_cache_del_attrval_list(tmpDn);
+ if(spec)
+ cos_cache_del_attrval_list(spec);
+ if(pAttrs)
+ cos_cache_del_attrval_list(pAttrs);
+ if(theDef)
+ slapi_ch_free((void**)&theDef);
+ }
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_add_defn\n",0,0,0);
+ return ret;
+}
+
+/*
+ cos_cache_del_attrval_list
+ --------------------------
+ walks the list deleting as it goes
+*/
+static void cos_cache_del_attrval_list(cosAttrValue **pVal)
+{
+ LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_del_attrval_list\n",0,0,0);
+
+ while(*pVal)
+ {
+ cosAttrValue *pTmp = (*pVal)->list.pNext;
+
+ slapi_ch_free((void**)&((*pVal)->val));
+ slapi_ch_free((void**)&(*pVal));
+ *pVal = pTmp;
+ }
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_del_attrval_list\n",0,0,0);
+}
+
+
+/*
+ cos_cache_add_attrval
+ ---------------------
+ adds a value to an attribute value list
+*/
+static int cos_cache_add_attrval(cosAttrValue **attrval, char *val)
+{
+ int ret = 0;
+ cosAttrValue *theVal;
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_add_attrval\n",0,0,0);
+
+ /* create the attrvalue */
+ theVal = (cosAttrValue*) slapi_ch_malloc(sizeof(cosAttrValue));
+ if(theVal)
+ {
+ theVal->val = slapi_ch_strdup(val);
+ if(theVal->val)
+ {
+ cos_cache_add_ll_entry((void**)attrval, theVal, NULL);
+ }
+ else
+ {
+ slapi_ch_free((void**)&theVal);
+ LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_add_attrval: failed to allocate memory\n",0,0,0);
+ ret = -1;
+ }
+ }
+ else
+ {
+ LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_add_attrval: failed to allocate memory\n",0,0,0);
+ ret = -1;
+ }
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_add_attrval\n",0,0,0);
+ return ret;
+}
+
+/*
+ cos_cache_add_ll_entry - RECURSIVE for sorted lists
+ ---------------------------------------------------
+ if a compare function is passed as argument, the element
+ is added to the linked list in the sorted order according
+ to that compare functions algorithm. This prepares the list
+ for ultra fast indexing - requiring only to walk the list once
+ to build the index.
+ if no compare function is passed, the element is added
+ to the head of the linked list
+ the index is created after the linked list is complete,
+ and so is always null until explicitly indexed
+
+ *NOTE* this function assumes and *requires* that the structures
+ passed to it in "attrval" and "theVal" have a cosIndexedLinkedList
+ member, and it is the *first* member of the structure. This
+ is safe because this is a module level function, and all functions
+ which call this one are part of the same sub-system.
+
+ The compare function will also make a similar assumption, but
+ likely one that recognizes the full structure or type, it is
+ the responsibility of the caller to match input structures with
+ the appropriate compare function.
+
+ WARNING: current recursive sorting code never used, never tested
+*/
+static void cos_cache_add_ll_entry(void** attrval, void *theVal, int ( *compare )(const void *elem1, const void *elem2 ))
+{
+ static cosIndexedLinkedList *pLastList = 0;
+ static cosIndexedLinkedList *first_element;
+ static int call_count = 0;
+
+ call_count++;
+ LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_add_ll_entry - recursion level %d\n",call_count,0,0);
+
+
+ /*
+ need to save the first element of the list
+ we update this first element whenever an entry
+ is added to the start of the list, this way
+ we can ensure that the head of the list is always
+ *attrval - callers pass us the head of the list
+ and can expect that what they get back is also
+ the head of the list
+ */
+ if(call_count == 1)
+ first_element = *attrval;
+
+ if(*attrval)
+ {
+ if(compare == NULL)
+ {
+ /* we dont want this list sorted */
+ /* push this to the start of the list (because its quick) */
+ ((cosIndexedLinkedList*)theVal)->pNext = *attrval;
+ ((cosIndexedLinkedList*)theVal)->index = NULL;
+ *attrval = theVal;
+ }
+ else
+ {
+ /* insert this in the list in sorted order
+ (because its quicker for building indexes later) */
+ int comp_ret = 0;
+
+ comp_ret = compare(*attrval, theVal);
+ if(comp_ret == 0 || comp_ret > 0)
+ {
+ /* insert prior to this element */
+ if(pLastList)
+ pLastList->pNext = theVal;
+ else
+ first_element = theVal;
+
+ ((cosIndexedLinkedList*)theVal)->pNext = *attrval;
+ ((cosIndexedLinkedList*)theVal)->index = NULL;
+ }
+ else
+ {
+ /* still looking - recurse on next element */
+ pLastList = (cosIndexedLinkedList*)attrval;
+ cos_cache_add_ll_entry(&(((cosIndexedLinkedList*)attrval)->pNext), theVal, compare);
+ }
+
+ if(call_count == 1)
+ *attrval = first_element;
+ }
+ }
+ else
+ {
+ /* new or end of list */
+ ((cosIndexedLinkedList*)theVal)->pNext = NULL;
+ ((cosIndexedLinkedList*)theVal)->index = NULL;
+ if(call_count == 1) /* if new list */
+ *attrval = theVal;
+ }
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_add_ll_entry - recursion level %d\n",call_count,0,0);
+ call_count--;
+}
+
+
+/*
+ cos_cache_getref
+ ----------------
+ retrieves a reference to the current cache and adds to the cache reference count
+*/
+int cos_cache_getref(cos_cache **pptheCache)
+{
+ int ret = -1;
+ static int first_time = 1;
+ cosCache **ppCache = (cosCache**)pptheCache;
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_getref\n",0,0,0);
+
+ if(first_time)
+ {
+ first_time = 0;
+ /* first customer, create the cache */
+ slapi_lock_mutex(change_lock);
+ if(pCache == NULL)
+ {
+ if(cos_cache_create())
+ {
+ /* there was a problem or no COS definitions were found */
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "cos_cache_getref: no cos cache created\n",0,0,0);
+ }
+ }
+ slapi_unlock_mutex(change_lock);
+ }
+
+ slapi_lock_mutex(cache_lock);
+
+ *ppCache = pCache;
+
+ if(pCache)
+ ret = ++((*ppCache)->refCount);
+
+ slapi_unlock_mutex(cache_lock);
+
+
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_getref\n",0,0,0);
+ return ret;
+}
+
+/*
+ cos_cache_addref
+ ----------------
+ adds 1 to the cache reference count
+*/
+int cos_cache_addref(cos_cache *ptheCache)
+{
+ int ret;
+ cosCache *pCache = (cosCache*)ptheCache;
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_addref\n",0,0,0);
+
+ slapi_lock_mutex(cache_lock);
+
+ if(pCache)
+ ret = ++(pCache->refCount);
+
+ slapi_unlock_mutex(cache_lock);
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_addref\n",0,0,0);
+
+ return ret;
+}
+
+
+/*
+ cos_cache_release
+ -----------------
+ subtracts 1 from the cache reference count, if the count falls
+ below 1, the cache is destroyed.
+*/
+int cos_cache_release(cos_cache *ptheCache)
+{
+ int ret = 0;
+ int destroy = 0;
+ cosCache *pOldCache = (cosCache*)ptheCache;
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_release\n",0,0,0);
+
+ slapi_lock_mutex(cache_lock);
+
+ if(pOldCache)
+ {
+ ret = --(pOldCache->refCount);
+ if(ret == 0)
+ destroy = 1;
+ }
+
+ slapi_unlock_mutex(cache_lock);
+
+ if(destroy)
+ {
+ cosDefinitions *pDef = pOldCache->pDefs;
+
+ /* now is the first time it is
+ * safe to assess whether
+ * vattr caching can be turned on
+ */
+ if(pCache && pCache->vattr_cacheable)
+ {
+ slapi_vattrcache_cache_all();
+ }
+
+ /* destroy the cache here - no locking required, no references outstanding */
+
+ if(pDef)
+ cos_cache_del_schema(pOldCache);
+
+ while(pDef)
+ {
+ cosDefinitions *pTmpD = pDef;
+ cosTemplates *pCosTmps = pDef->pCosTmps;
+
+ while(pCosTmps)
+ {
+ cosTemplates *pTmpT = pCosTmps;
+
+ pCosTmps = pCosTmps->list.pNext;
+
+ cos_cache_del_attr_list(&(pTmpT->pAttrs));
+ cos_cache_del_attrval_list(&(pTmpT->pObjectclasses));
+ cos_cache_del_attrval_list(&(pTmpT->pDn));
+ slapi_ch_free((void**)&(pTmpT->cosGrade));
+ slapi_ch_free((void**)&pTmpT);
+ }
+
+ pDef = pDef->list.pNext;
+
+ cos_cache_del_attrval_list(&(pTmpD->pDn));
+ cos_cache_del_attrval_list(&(pTmpD->pCosTargetTree));
+ cos_cache_del_attrval_list(&(pTmpD->pCosTemplateDn));
+ cos_cache_del_attrval_list(&(pTmpD->pCosSpecifier));
+ cos_cache_del_attrval_list(&(pTmpD->pCosAttrs));
+ cos_cache_del_attrval_list(&(pTmpD->pCosOverrides));
+ cos_cache_del_attrval_list(&(pTmpD->pCosOperational));
+ cos_cache_del_attrval_list(&(pTmpD->pCosMerge));
+ cos_cache_del_attrval_list(&(pTmpD->pCosOpDefault));
+ slapi_ch_free((void**)&pTmpD);
+ }
+
+ if(pOldCache->ppAttrIndex)
+ slapi_ch_free((void**)&(pOldCache->ppAttrIndex));
+ if(pOldCache->ppTemplateList)
+ slapi_ch_free((void**)&(pOldCache->ppTemplateList));
+ slapi_ch_free((void**)&pOldCache);
+ }
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_release\n",0,0,0);
+
+ return ret;
+}
+
+/*
+ cos_cache_attrval_count
+ -----------------------
+ counts the number of values in the list
+*/
+
+static int cos_cache_attrval_count(cosAttrValue *pVal)
+{
+ int ret = 0;
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_attrval_count\n",0,0,0);
+
+ while(pVal)
+ {
+ ret++;
+ pVal = pVal->list.pNext;
+ }
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_attrval_count\n",0,0,0);
+ return ret;
+}
+
+/*
+ cos_cache_del_attr_list
+ -----------------------
+ walk the list deleting as we go
+*/
+static void cos_cache_del_attr_list(cosAttributes **pAttrs)
+{
+ LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_del_attr_list\n",0,0,0);
+
+ while(*pAttrs)
+ {
+ cosAttributes *pTmp = (*pAttrs)->list.pNext;
+
+ cos_cache_del_attrval_list(&((*pAttrs)->pAttrValue));
+ slapi_ch_free((void**)&((*pAttrs)->pAttrName));
+ slapi_ch_free((void**)&(*pAttrs));
+ *pAttrs = pTmp;
+ }
+
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_del_attr_list\n",0,0,0);
+}
+
+
+/*
+ cos_cache_del_schema
+ --------------------
+ delete the object class lists used for schema checking
+*/
+static void cos_cache_del_schema(cosCache *pCache)
+{
+ char *pLastName = 0;
+ cosAttrValue *pLastRef = 0;
+ int attr_index = 0;
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_del_schema\n",0,0,0);
+
+ if(pCache && pCache->attrCount && pCache->ppAttrIndex)
+ {
+ pLastName = pCache->ppAttrIndex[0]->pAttrName;
+ pLastRef = pCache->ppAttrIndex[0]->pObjectclasses;
+
+ for(attr_index=1; attr_index<pCache->attrCount; attr_index++)
+ {
+ if(slapi_utf8casecmp((unsigned char*)pCache->ppAttrIndex[attr_index]->pAttrName, (unsigned char*)pLastName))
+ {
+ /* remember what went before */
+ pLastName = pCache->ppAttrIndex[attr_index]->pAttrName;
+
+ /* then blow it away */
+ cos_cache_del_attrval_list(&(pCache->ppAttrIndex[attr_index]->pObjectclasses));
+ }
+ }
+ }
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_del_schema\n",0,0,0);
+}
+
+
+/*
+ cos_cache_add_attr
+ ------------------
+ Adds an attribute to the list
+*/
+static int cos_cache_add_attr(cosAttributes **pAttrs, char *name, cosAttrValue *val)
+{
+ int ret =0;
+ cosAttributes *theAttr;
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_add_attr\n",0,0,0);
+
+ /* create the attribute */
+ theAttr = (cosAttributes*) slapi_ch_malloc(sizeof(cosAttributes));
+ if(theAttr)
+ {
+ theAttr->pAttrValue = val;
+ theAttr->pObjectclasses = 0; /* schema issues come later */
+ theAttr->pAttrName = slapi_ch_strdup(name);
+ if(theAttr->pAttrName)
+ {
+ cos_cache_add_ll_entry((void**)pAttrs, theAttr, NULL);
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "cos_cache_add_attr: Added attribute %s\n",name,0,0);
+ }
+ else
+ {
+ slapi_ch_free((void**)&theAttr);
+ LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_add_attr: failed to allocate memory\n",0,0,0);
+ ret = -1;
+ }
+ }
+ else
+ {
+ LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_add_attr: failed to allocate memory\n",0,0,0);
+ ret = -1;
+ }
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_add_attr\n",0,0,0);
+ return ret;
+}
+
+
+/*
+ cos_cache_add_tmpl
+ ------------------
+ Adds a template to the list
+*/
+static int cos_cache_add_tmpl(cosTemplates **pTemplates, cosAttrValue *dn, cosAttrValue *objclasses, cosAttrValue *pCosSpecifier, cosAttributes *pAttrs,cosAttrValue *cosPriority)
+{
+ int ret =0;
+ cosTemplates *theTemp;
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_add_tmpl\n",0,0,0);
+
+ /* create the attribute */
+ theTemp = (cosTemplates*) slapi_ch_malloc(sizeof(cosTemplates));
+ if(theTemp)
+ {
+ char *grade = (char*)slapi_ch_malloc(strlen(dn->val)+1);
+ int grade_index = 0;
+ int index = 0;
+ int template_default = 0;
+
+ slapi_dn_normalize(dn->val);
+
+ /* extract the cos grade */
+ while(dn->val[index] != '=' && dn->val[index] != '\0')
+ index++;
+
+ if(dn->val[index] == '=')
+ {
+ int quotes = 0;
+
+ index++;
+
+ /* copy the grade (supports one level of quote nesting in rdn) */
+ while(dn->val[index] != ',' || dn->val[index-1] == '\\' || quotes == 1)
+ {
+ if(dn->val[index] == '"')
+ {
+ if(quotes == 0)
+ quotes = 1;
+ else
+ quotes = 0;
+ }
+ else
+ {
+ if(dn->val[index] != '\\') /* skip escape chars */
+ {
+ grade[grade_index] = dn->val[index];
+ grade_index++;
+ }
+ }
+
+ index++;
+ }
+
+ grade[grade_index] = '\0';
+
+ /* ok, grade copied, is it the default template? */
+
+ if(pCosSpecifier) /* some schemes don't have one */
+ {
+ char tmpGrade[BUFSIZ];
+
+ if (strlen(pCosSpecifier->val) < (BUFSIZ - 9)) { /* 9 for "-default" */
+ strcpy(tmpGrade, pCosSpecifier->val);
+ strcat(tmpGrade, "-default");
+ if(!slapi_utf8casecmp((unsigned char*)grade, (unsigned char*)tmpGrade))
+ template_default = 1;
+ }
+ else
+ {
+ /*
+ * We shouldn't pass ever through this code as we expect
+ * pCosSpecifier values to be reasonably smaller than BUFSIZ
+ *
+ * only 2 lines of code -> no need to set an indirect char *
+ * duplicate the lines of code for clearness instead
+ */
+ char * newTmpGrade = (char*) slapi_ch_malloc(
+ strlen((pCosSpecifier->val) + 9));
+ strcpy(newTmpGrade, pCosSpecifier->val);
+ strcat(newTmpGrade, "-default");
+ if(!slapi_utf8casecmp((unsigned char*)grade, (unsigned char*)newTmpGrade))
+ template_default = 1;
+ slapi_ch_free((void**)&newTmpGrade);
+ }
+ }
+
+ }
+ else
+ {
+ /* mmm, should never get here, it means the dn is whacky */
+ LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_add_tmpl: malformed dn detected: %s\n",dn,0,0);
+ grade[0] = '\0';
+ }
+
+ /* now fill in the blanks */
+ theTemp->pDn = dn;
+ theTemp->pObjectclasses = objclasses;
+ theTemp->pAttrs = pAttrs;
+ theTemp->cosGrade = slapi_ch_strdup(grade);
+ theTemp->template_default = template_default;
+ theTemp->cosPriority = (unsigned long)-1;
+
+ if(cosPriority)
+ {
+ theTemp->cosPriority = atol(cosPriority->val);
+ cos_cache_del_attrval_list(&cosPriority);
+ }
+
+ cos_cache_add_ll_entry((void**)pTemplates, theTemp, NULL);
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "cos_cache_add_tmpl: Added template %s\n",dn->val,0,0);
+
+ slapi_ch_free((void**)&grade);
+ }
+ else
+ {
+ LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_add_tmpl: failed to allocate memory\n",0,0,0);
+ ret = -1;
+ }
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_add_tmpl\n",0,0,0);
+ return ret;
+}
+
+/*
+ cos_cache_attrval_exists
+ ------------------------
+ performs linear search on the list for a
+ cosAttrValue that matches val
+ however, if the "index" member of cosAttrValue
+ is non-null then a binary search is performed
+ on the index {PARPAR: TO BE DONE}
+
+ return 1 on found, 0 otherwise
+*/
+static int cos_cache_attrval_exists(cosAttrValue *pAttrs, const char *val)
+{
+ int ret = 0;
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_attrval_exists\n",0,0,0);
+
+ while(pAttrs)
+ {
+ if(!slapi_utf8casecmp((unsigned char*)pAttrs->val,(unsigned char*)val))
+ {
+ ret = 1;
+ break;
+ }
+
+ pAttrs = pAttrs->list.pNext;
+ }
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_attrval_exists\n",0,0,0);
+ return ret;
+}
+
+
+
+static int cos_cache_vattr_get(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, char *type, Slapi_ValueSet** results,int *type_name_disposition, char** actual_type_name, int flags, int *free_flags, void *hint)
+{
+ int ret = -1;
+ cos_cache *pCache = 0;
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_vattr_get\n",0,0,0);
+
+ if(cos_cache_getref(&pCache) < 1)
+ {
+ /* problems we are hosed */
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "cos_cache_vattr_get: failed to get class of service reference\n",0,0,0);
+ goto bail;
+ }
+
+ ret = cos_cache_query_attr(pCache, c, e, type, results, NULL, NULL, NULL);
+ if(ret == 0)
+ {
+ *free_flags = SLAPI_VIRTUALATTRS_RETURNED_COPIES;
+ *actual_type_name = slapi_ch_strdup(type);
+ *type_name_disposition = SLAPI_VIRTUALATTRS_TYPE_NAME_MATCHED_EXACTLY_OR_ALIAS;
+ }
+ cos_cache_release(pCache);
+
+bail:
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_vattr_get\n",0,0,0);
+ return ret;
+}
+
+
+static int cos_cache_vattr_compare(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, char *type, Slapi_Value *test_this, int* result, int flags, void *hint)
+{
+ int ret = -1;
+ cos_cache *pCache = 0;
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_vattr_compare\n",0,0,0);
+
+ if(cos_cache_getref(&pCache) < 1)
+ {
+ /* problems we are hosed */
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "cos_cache_vattr_compare: failed to get class of service reference\n",0,0,0);
+ goto bail;
+ }
+
+ ret = cos_cache_query_attr(pCache, c, e, type, NULL, test_this, result, NULL);
+
+ cos_cache_release(pCache);
+
+bail:
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_vattr_compare\n",0,0,0);
+ return ret;
+}
+
+/*
+ * this imp is damn slow
+ *
+ */
+static int cos_cache_vattr_types(vattr_sp_handle *handle,Slapi_Entry *e,
+ vattr_type_list_context *type_context,int flags)
+{
+ int ret = 0;
+ int index = 0;
+ cosCache *pCache;
+ char *lastattr = "thisisfakeforcos";
+ int props = 0;
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_vattr_types\n",0,0,0);
+
+ if(cos_cache_getref((cos_cache **)&pCache) < 1)
+ {
+ /* problems we are hosed */
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "cos_cache_vattr_types: failed to get class of service reference\n",0,0,0);
+ goto bail;
+ }
+
+ while(index < pCache->attrCount )
+ {
+ if(slapi_utf8casecmp(
+ (unsigned char *)pCache->ppAttrIndex[index]->pAttrName,
+ (unsigned char *)lastattr))
+ {
+ lastattr = pCache->ppAttrIndex[index]->pAttrName;
+
+ if(1 == cos_cache_query_attr(pCache, NULL, e, lastattr, NULL, NULL,
+ NULL, &props))
+ {
+ /* entry contains this attr */
+ vattr_type_thang thang = {0};
+
+ thang.type_name = lastattr;
+ thang.type_flags = props;
+
+ slapi_vattrspi_add_type(type_context,&thang,0);
+ }
+
+ }
+
+ index++;
+ }
+
+ cos_cache_release(pCache);
+
+bail:
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_vattr_types\n",0,0,0);
+ return ret;
+}
+
+
+/*
+ cos_cache_query_attr
+ --------------------
+ queries the cache to determine if this entry
+ should have an attribute of "type", and if so,
+ returns the attribute values in "vals" - which
+ should be subsequently freed by a call to
+ cos_cache_query_attr_free()
+
+ returns
+ 0 on success, we added a computed attribute
+ 1 on outright failure
+ -1 when doesn't know about attribute
+
+ {PARPAR} must also check the attribute does not exist if we are not
+ overriding and allow the DS logic to pick it up by denying knowledge
+ of attribute
+*/
+static int cos_cache_query_attr(cos_cache *ptheCache, vattr_context *context, Slapi_Entry *e, char *type, Slapi_ValueSet **out_attr, Slapi_Value *test_this, int *result, int *props)
+{
+ int ret = -1;
+ cosCache *pCache = (cosCache*)ptheCache;
+ char *pDn = 0;
+ Slapi_Attr *pObjclasses = 0;
+ int attr_index = 0; /* for looping through attributes */
+ int attr_matched_index = 0; /* for identifying the matched attribute */
+ int hit = 0;
+ cosAttributes *pDefAttr = 0;
+ Slapi_ValueSet* results = 0;
+ Slapi_Value *val;
+/* int type_name_disposition;
+ char *actual_type_name;
+ int flags = 0;
+ int free_flags;*/
+ Slapi_Attr *pTmpVals;
+ int using_default = 0;
+ int entry_has_value = 0;
+ int merge_mode = 0;
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_query_attr\n",0,0,0);
+
+
+ if(out_attr)
+ *out_attr = 0;
+
+ /*
+ to perform this operation we need to know:
+
+ * if we know about the attribute, if not just exit
+ --
+ * dn,to determine its relevancy to any cos definition,
+ it must be a child of cosTargetTree
+ --
+ * objectclasses,to determine if the cos attribute will
+ violate schema (only when schema checking is on)
+ --
+ * class of service specifier, for matching definitions
+ - cosSpecifier is the attribute name and is used to
+ determine the cosDefinition to use, its value determines
+ the template to use
+ --
+ * the cosAttribute(s) (from the cosDefinition(s)) that match
+ the attribute name.
+ ($$)If these have a postfix of "default", then it is the same
+ as no postfix i.e. this acts as the default value. If it has
+ a postfix of "override", then the value in the matching template
+ is used regardless of any value stored in the entry. This has
+ been worked out previously so we can use a bool indicator in
+ the cosDefinition structure to determine what to do.
+ --
+ * the value of the attribute in the entry -
+ if present it overrides any default template value (see $$)
+
+ Also this ordering ensures least work done to fail (in this
+ implementation)
+ */
+
+ /** attribute **/
+ /*
+ lets be sure we need to do something
+ most of the time we probably don't
+ */
+ attr_index = cos_cache_find_attr(pCache, type);
+ if(attr_index == -1)
+ {
+ /* we don't know about this attribute */
+ goto bail;
+ }
+
+ /*
+ if there is a value in the entry the outcome
+ of the cos attribute resolution may be different
+ */
+ slapi_entry_attr_find(e, type, &pTmpVals);
+ if(pTmpVals)
+ entry_has_value = 1;
+
+ /** dn **/
+ pDn = slapi_entry_get_dn(e);
+
+ if(pDn == 0)
+ {
+ LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_query_attr: failed to get entry dn\n",0,0,0);
+ ret = 1;
+ goto bail;
+ }
+
+ slapi_dn_normalize( pDn );
+
+ /** objectclasses **/
+ if(pCache->ppAttrIndex[attr_index]->attr_operational == 0 && config_get_schemacheck() &&
+ pCache->ppAttrIndex[attr_index]->attr_operational_default == 0)
+ {
+ /* does this entry violate schema? */
+
+ if(slapi_entry_attr_find( e, "objectclass", &pObjclasses ))
+ {
+ LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_query_attr: failed to get objectclass from %s\n",pDn,0,0);
+ goto bail;
+ }
+
+ if(!cos_cache_schema_check(pCache, attr_index, pObjclasses))
+ {
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "cos_cache_query_attr: cos attribute %s failed schema check on dn: %s\n",type,pDn,0);
+ goto bail;
+ }
+ }
+
+ /** class of service specifier **/
+ /*
+ now we need to iterate through the attributes to discover
+ if one fits all the criteria, we'll take the first that does
+ and blow off the rest
+ */
+ do
+ {
+ /* for convenience, define some pointers */
+ cosAttributes *pAttr = pCache->ppAttrIndex[attr_index];
+ cosTemplates *pTemplate = (cosTemplates*)pAttr->pParent;
+ cosDefinitions *pDef = (cosDefinitions*)pTemplate->pParent;
+ cosAttrValue *pTargetTree = pDef->pCosTargetTree;
+
+ /* now for the tests */
+
+ /* would we be allowed to supply this attribute if we had one? */
+ if(entry_has_value && pAttr->attr_override == 0 && pAttr->attr_operational == 0)
+ {
+ /* answer: no, move on to the next attribute */
+ attr_index++;
+ continue;
+ }
+
+ /* if we are in merge_mode, can the attribute be merged? */
+ if(merge_mode && pAttr->attr_cos_merge == 0)
+ {
+ /* answer: no, move on to the next attribute */
+ attr_index++;
+ continue;
+ }
+
+ /* is this entry a child of the target tree(s)? */
+ do
+ {
+ if(pTargetTree)
+ slapi_dn_normalize( pTargetTree->val );
+
+ if( pTargetTree->val == 0 ||
+ slapi_dn_issuffix(pDn, pTargetTree->val) != 0 ||
+ (views_api && views_entry_exists(views_api, pTargetTree->val, e)) /* might be in a view */
+ )
+ {
+ cosAttrValue *pSpec = pDef->pCosSpecifier;
+ Slapi_ValueSet *pAttrSpecs = 0;
+
+
+ /* Does this entry have a correct cosSpecifier? */
+ do
+ {
+ Slapi_ValueSet *results = 0;
+ int type_name_disposition = 0;
+ char *actual_type_name = 0;
+ int free_flags = 0;
+
+ if(pSpec && pSpec->val) {
+ slapi_vattr_values_get_sp(context, e, pSpec->val, &pAttrSpecs, &type_name_disposition, &actual_type_name, 0, &free_flags);
+ /* MAB: We need to free actual_type_name here !!!
+ XXX BAD--should use slapi_vattr_values_free() */
+ slapi_ch_free((void **) &actual_type_name);
+ }
+
+ if(pAttrSpecs || pDef->cosType == COSTYPE_POINTER)
+ {
+ int index = 0;
+
+ /* does the cosSpecifier value correspond to this template? */
+ if(pDef->cosType == COSTYPE_INDIRECT)
+ {
+ /*
+ it always does correspond for indirect schemes (it's a dummy value)
+ now we must follow the dn of our pointer and retrieve a value to
+ return
+ Note: we support one dn only, the result of multiple pointers is undefined
+ */
+ Slapi_Value *indirectdn;
+ int pointer_flags = 0;
+
+ slapi_valueset_first_value( pAttrSpecs, &indirectdn );
+
+ if(props)
+ pointer_flags = *props;
+
+ if( indirectdn != NULL &&
+ !cos_cache_follow_pointer( context, (char*)slapi_value_get_string(indirectdn), type, out_attr, test_this, result, pointer_flags))
+ hit = 1;
+ }
+ else
+ {
+ if(pDef->cosType != COSTYPE_POINTER)
+ index = slapi_valueset_first_value( pAttrSpecs, &val );
+
+ while(pDef->cosType == COSTYPE_POINTER || val)
+ {
+ if(pDef->cosType == COSTYPE_POINTER || !slapi_utf8casecmp((unsigned char*)pTemplate->cosGrade, (unsigned char*)slapi_value_get_string(val)))
+ {
+ /* we have a hit */
+
+
+ if(out_attr)
+ {
+ if(cos_cache_cos_2_slapi_valueset(pAttr, out_attr) == 0)
+ hit = 1;
+ else
+ {
+ LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_query_attr: could not create values to return\n",0,0,0);
+ goto bail;
+ }
+
+ if(pAttr->attr_cos_merge)
+ {
+ merge_mode = 1;
+ attr_matched_index = attr_index;
+ }
+ }
+ else
+ {
+ if(test_this && result)
+ {
+ /* compare op */
+ if(cos_cache_cmp_attr(pAttr, test_this, result))
+ {
+ hit = 1;
+ }
+ }
+ else
+ {
+ /* well, this must be a request for type only */
+ hit = 1;
+ }
+ }
+
+ break;
+ }
+
+ if(pDef->cosType != COSTYPE_POINTER)
+ index = slapi_valueset_next_value( pAttrSpecs, index, &val );
+ }
+ }
+ }
+
+ if(pSpec)
+ pSpec = pSpec->list.pNext;
+
+ } while(hit == 0 && pSpec);
+
+ /* MAB: We need to free pAttrSpecs here !!!
+ XXX BAD--should use slapi_vattr_values_free()*/
+ slapi_valueset_free(pAttrSpecs);
+
+ /* is the cosTemplate the default template? */
+ if(hit == 0 && pTemplate->template_default && !pDefAttr)
+ {
+ /* then lets save the attr in case we need it later */
+ pDefAttr = pAttr;
+ }
+ }
+
+ pTargetTree = pTargetTree->list.pNext;
+
+ } while(hit == 0 && pTargetTree);
+
+
+ if(hit==0 || merge_mode)
+ attr_index++;
+
+ } while(
+ (hit == 0 || merge_mode) &&
+ pCache->attrCount > attr_index &&
+ !slapi_utf8casecmp((unsigned char*)type, (unsigned char*)pCache->ppAttrIndex[attr_index]->pAttrName)
+ );
+
+ if(!merge_mode)
+ attr_matched_index = attr_index;
+
+ /* should we use a default value? */
+ if(hit == 0 && pDefAttr)
+ {
+ /* we have a hit */
+
+ using_default = 1;
+
+ if(out_attr)
+ {
+ if(cos_cache_cos_2_slapi_valueset(pDefAttr, out_attr) == 0)
+ hit = 1;
+ else
+ {
+ LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_query_attr: could not create values to return\n",0,0,0);
+ goto bail;
+ }
+ }
+ else
+ {
+ if(test_this && result)
+ {
+ /* compare op */
+ if(cos_cache_cmp_attr(pDefAttr, test_this, result))
+ hit = 1;
+ }
+ else
+ {
+ /* well, this must be a request for type only and the entry gets default template value */
+ hit = 1;
+ }
+ }
+ }
+
+ if(hit == 1 && out_attr == NULL && test_this == NULL)
+ ret = 1;
+ else if(hit == 1)
+ ret = 0;
+
+ if(props)
+ *props = 0;
+
+ if(hit == 1 && props && pDefAttr) {
+ if (
+ ((using_default && pDefAttr->attr_operational == 1) ||
+ (!using_default && pCache->ppAttrIndex[attr_matched_index]->attr_operational == 1)) ||
+ ((using_default && pDefAttr->attr_operational_default == 1) ||
+ (!using_default && pCache->ppAttrIndex[attr_matched_index]->attr_operational_default == 1)) )
+ {
+ /* this is an operational attribute, lets mark it so */
+ *props |= SLAPI_ATTR_FLAG_OPATTR;
+ }
+ }
+
+bail:
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_query_attr\n",0,0,0);
+ return ret;
+}
+
+/*
+ cos_cache_query_attr_free
+ -------------------------
+ frees the memory allocated for the data returned
+ by cos_cache_query_attr
+*/
+static void cos_cache_query_attr_free(struct berval ***vals)
+{
+ int index = 0;
+
+ while((*vals)[index])
+ {
+ slapi_ch_free((void**)&((*vals)[index]));
+ index++;
+ }
+
+ slapi_ch_free((void**)*vals);
+}
+
+/*
+ cos_cache_find_attr
+ -------------------
+ searches for the attribute "type", and if found returns the index
+ of the first occurrance of the attribute in the cache top level
+ indexed attribute list.
+*/
+static int cos_cache_find_attr(cosCache *pCache, char *type)
+{
+ int ret = -1; /* assume failure */
+ cosAttributes attr;
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_find_attr\n",0,0,0);
+
+ attr.pAttrName = type;
+
+ if(pCache->attrCount-1 != 0)
+ ret = cos_cache_attr_index_bsearch(pCache, &attr, 0, pCache->attrCount-1);
+ else
+ {
+ /* only one attribute (that will fool our bsearch) lets check it here */
+ if(!slapi_utf8casecmp((unsigned char*)type, (unsigned char*)(pCache->ppAttrIndex)[0]->pAttrName))
+ {
+ ret = 0;
+ }
+ }
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_find_attr\n",0,0,0);
+ return ret;
+}
+
+
+/*
+ cos_cache_schema_check
+ ----------------------
+ check those object classes which match in the input list and the
+ cached set for allowed attribute types
+
+ return non-null for schema matches, zero otherwise
+*/
+static int cos_cache_schema_check(cosCache *pCache, int attr_index, Slapi_Attr *pObjclasses)
+{
+ int ret = 0; /* assume failure */
+ Slapi_Value *val;
+ int hint;
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_schema_check\n",0,0,0);
+
+ hint = slapi_attr_first_value( pObjclasses, &val );
+ while(hint != -1)
+ {
+ ret = cos_cache_attrval_exists(pCache->ppAttrIndex[attr_index]->pObjectclasses, (char*) slapi_value_get_string(val));
+ if(ret)
+ break;
+
+ hint = slapi_attr_next_value( pObjclasses, hint, &val );
+ }
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_schema_check\n",0,0,0);
+ return ret;
+}
+
+/*
+ cos_cache_schema_build
+ ----------------------
+ For each attribute in our global cache add the objectclasses which allow it.
+ This may be referred to later to check schema is not being violated.
+*/
+static int cos_cache_schema_build(cosCache *pCache)
+{
+ int ret = 0; /* we assume success, with operational attributes not supplied in schema we might fail otherwise */
+ struct objclass *oc;
+ char *pLastName = 0;
+ cosAttrValue *pLastRef = 0;
+ int attr_index = 0;
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_schema_build\n",0,0,0);
+
+ if(!config_get_schemacheck())
+ ret = 0;
+
+ /*
+ it is expected that in all but the most hard core cases, the number of
+ objectclasses will out number the attributes we look after - so we make
+ the objectclasses the outside loop. However, even if they are not, we
+ perform binary searches on the attribute list anyway, so it should be
+ considerably faster to search than the linked list of objectclasses (even
+ with the string comparisons going on)
+ */
+ oc_lock_read();
+ for ( oc = g_get_global_oc_nolock(); oc != NULL; oc = oc->oc_next )
+ {
+ char **pppAttrs[2];
+ int index;
+ int attrType = 0;
+
+ pppAttrs[0] = oc->oc_required;
+ pppAttrs[1] = oc->oc_allowed;
+
+ /* we need to check both required and allowed attributes I think */
+ while(attrType < 2)
+ {
+ if(pppAttrs[attrType])
+ {
+ index = 0;
+
+ while(pppAttrs[attrType][index])
+ {
+ attr_index = cos_cache_find_attr(pCache, pppAttrs[attrType][index]);
+ if(attr_index != -1)
+ {
+ /*
+ this attribute is one of ours, add this
+ objectclass to the objectclass list
+ note the index refers to the first
+ occurrence of this attribute in the list,
+ later we will copy over references to this
+ list to all the other attribute duplicates.
+ */
+
+ cos_cache_add_attrval(&(pCache->ppAttrIndex[attr_index]->pObjectclasses), oc->oc_name);
+ ret = 0;
+ }
+ index++;
+ }
+ }
+
+ attrType++;
+ }
+ }
+ oc_unlock();
+
+ /*
+ OK, now we need to add references to the real
+ lists to the duplicate attribute entries.
+ (this allows the schema check to be a little
+ less complex and just a little quicker)
+ */
+ pLastName = pCache->ppAttrIndex[0]->pAttrName;
+ pLastRef = pCache->ppAttrIndex[0]->pObjectclasses;
+
+ for(attr_index=1; attr_index<pCache->attrCount; attr_index++)
+ {
+ if(!slapi_utf8casecmp((unsigned char*)pCache->ppAttrIndex[attr_index]->pAttrName, (unsigned char*)pLastName))
+ {
+ /* copy over reference */
+ pCache->ppAttrIndex[attr_index]->pObjectclasses = pLastRef;
+ }
+ else
+ {
+ /* remember what went before */
+ pLastName = pCache->ppAttrIndex[attr_index]->pAttrName;
+ pLastRef = pCache->ppAttrIndex[attr_index]->pObjectclasses;
+ }
+ }
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_schema_build\n",0,0,0);
+ return ret;
+}
+
+
+/*
+ cos_cache_index_all
+ -------------------
+ Indexes every attribute in the cache for fast binary lookup
+ on attributes from the top level of the cache.
+ Also fixes up all parent pointers so that a single attribute
+ lookup will allow access to all information regarding that attribute.
+ Attributes that appear more than once in the cache will also
+ be indexed more than once - this means that a pure binary
+ search is not possible, but it is possible to make use of a
+ duplicate entry aware binary search function - which are rare beasts,
+ so we'll need to provide cos_cache_attr_bsearch()
+
+ This is also a handy time to mark the attributes as overides if
+ necessary
+*/
+
+static int cos_cache_index_all(cosCache *pCache)
+{
+ int ret = -1;
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_index_all\n",0,0,0);
+
+ /*
+ first fixup the index array so we can use qsort()
+ also fixup the parent pointers
+ */
+
+ pCache->ppTemplateList = 0;
+ pCache->templateCount = 0;
+ pCache->ppAttrIndex = 0;
+
+ pCache->attrCount = cos_cache_total_attr_count(pCache);
+ if(pCache->attrCount && pCache->templateCount)
+ {
+ int tmpindex = 0;
+ int cmpindex = 0;
+ int actualCount = 0;
+
+ pCache->ppAttrIndex = (cosAttributes**)slapi_ch_malloc(sizeof(cosAttributes*) * pCache->attrCount);
+ pCache->ppTemplateList = (char**)slapi_ch_calloc((pCache->templateCount + 1) * 2, sizeof(char*));
+ if(pCache->ppAttrIndex && pCache->ppTemplateList)
+ {
+ int attrcount = 0;
+ cosDefinitions *pDef = pCache->pDefs;
+ cosAttrValue *pAttrVal = 0;
+
+ while(pDef)
+ {
+ cosTemplates *pCosTmps = pDef->pCosTmps;
+
+ while(pCosTmps)
+ {
+ cosAttributes *pAttrs = pCosTmps->pAttrs;
+
+ pCosTmps->pParent = pDef;
+
+ while(pAttrs)
+ {
+ pAttrs->pParent = pCosTmps;
+ (pCache->ppAttrIndex)[attrcount] = pAttrs;
+
+ if(cos_cache_attrval_exists(pDef->pCosOverrides, pAttrs->pAttrName))
+ pAttrs->attr_override = 1;
+ else
+ pAttrs->attr_override = 0;
+
+ if(cos_cache_attrval_exists(pDef->pCosOperational, pAttrs->pAttrName))
+ pAttrs->attr_operational = 1;
+ else
+ pAttrs->attr_operational = 0;
+
+ if(cos_cache_attrval_exists(pDef->pCosMerge, pAttrs->pAttrName))
+ pAttrs->attr_cos_merge = 1;
+ else
+ pAttrs->attr_cos_merge = 0;
+
+ if(cos_cache_attrval_exists(pDef->pCosOpDefault, pAttrs->pAttrName))
+ pAttrs->attr_operational_default = 1;
+ else
+ pAttrs->attr_operational_default = 0;
+
+ attrcount++;
+
+ pAttrs = pAttrs->list.pNext;
+ }
+
+ pCosTmps = pCosTmps->list.pNext;
+ }
+
+ /*
+ we need to build the template dn list too,
+ we are going to take care that we do not
+ add duplicate dns or dns that have
+ ancestors elsewhere in the list since this
+ list will be binary searched (with a special
+ BS alg) to find an ancestor tree for a target
+ that has been modified - that comes later in
+ this function however - right now we'll just
+ slap them in the list
+ */
+ pAttrVal = pDef->pCosTemplateDn;
+
+ while(pAttrVal)
+ {
+ slapi_dn_normalize(pAttrVal->val);
+ pCache->ppTemplateList[tmpindex] = pAttrVal->val;
+
+ tmpindex++;
+ pAttrVal = pAttrVal->list.pNext;
+ }
+
+ pDef = pDef->list.pNext;
+ }
+
+ /* now sort the index array */
+ qsort(pCache->ppAttrIndex, attrcount, sizeof(cosAttributes*), cos_cache_attr_compare);
+ qsort(pCache->ppTemplateList, tmpindex, sizeof(char*), cos_cache_string_compare);
+
+ /*
+ now we have the sorted template dn list, we can get rid of
+ duplicates and entries that have an ancestor elsewhere in
+ the list - all this in the name of faster searches
+ */
+
+ /* first go through zapping the useless PARPAR - THIS DOES NOT WORK */
+ tmpindex = 1;
+ cmpindex = 0;
+ actualCount = pCache->templateCount;
+
+ while(tmpindex < pCache->templateCount)
+ {
+ if(
+ !slapi_utf8casecmp((unsigned char*)pCache->ppTemplateList[tmpindex],(unsigned char*)pCache->ppTemplateList[cmpindex]) ||
+ slapi_dn_issuffix(pCache->ppTemplateList[tmpindex], pCache->ppTemplateList[cmpindex])
+ )
+ {
+ /* this guy is a waste of space */
+ pCache->ppTemplateList[tmpindex] = 0;
+ actualCount--;
+ }
+ else
+ cmpindex = tmpindex;
+
+ tmpindex++;
+ }
+
+ /* now shuffle everything up to the front to cover the bald spots */
+ tmpindex = 1;
+ cmpindex = 0;
+
+ while(tmpindex < pCache->templateCount)
+ {
+ if(pCache->ppTemplateList[tmpindex] != 0)
+ {
+ if(cmpindex)
+ {
+ pCache->ppTemplateList[cmpindex] = pCache->ppTemplateList[tmpindex];
+ pCache->ppTemplateList[tmpindex] = 0;
+ cmpindex++;
+ }
+ }
+ else
+ {
+ if(cmpindex == 0)
+ cmpindex = tmpindex;
+ }
+
+ tmpindex++;
+ }
+
+ pCache->templateCount = actualCount;
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "cos: cos cache index built\n",0,0,0);
+
+ ret = 0;
+ }
+ else
+ {
+ if(pCache->ppAttrIndex)
+ slapi_ch_free((void**)(&pCache->ppAttrIndex));
+
+ if(pCache->ppTemplateList)
+ slapi_ch_free((void**)(&pCache->ppTemplateList));
+
+ LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_index_all: failed to allocate index memory\n",0,0,0);
+ }
+ }
+ else
+ LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_index_all: no attributes to index\n",0,0,0);
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_index_all\n",0,0,0);
+ return ret;
+}
+
+
+/*
+ cos_cache_total_attr_count
+ --------------------------
+ walks the entire cache counting all attributes
+ note: this is coded so that it may be called
+ prior to the cache indexing of attributes - in
+ fact it is called by the code that creates the
+ index. Once indexing has been performed, it is
+ *much* *much* faster to get the count from the
+ cache object itself - cosCache::attrCount.
+
+ Additionally - a side effect is that the template
+ target trees are counted and placed in the cache level
+ target tree count - probably should be renamed,
+ but lets let it slide for now
+
+ returns the number of attributes counted
+*/
+static int cos_cache_total_attr_count(cosCache *pCache)
+{
+ int count = 0;
+ cosDefinitions *pDef = pCache->pDefs;
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_total_attr_count\n",0,0,0);
+
+ pCache->templateCount = 0;
+
+ while(pDef)
+ {
+ cosTemplates *pCosTmps = pDef->pCosTmps;
+
+ while(pCosTmps)
+ {
+ cosAttributes *pAttrs = pCosTmps->pAttrs;
+
+ while(pAttrs)
+ {
+ count++;
+ pAttrs = pAttrs->list.pNext;
+ }
+
+ pCache->templateCount++;
+ pCosTmps = pCosTmps->list.pNext;
+ }
+
+ pDef = pDef->list.pNext;
+ }
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_total_attr_count\n",0,0,0);
+ return count;
+}
+
+
+/*
+ cos_cache_XXX_compare
+ ---------------------
+ this set of functions are passed to sorting and searching
+ functions to provide an ordering comparison between to structures
+*/
+#if 0
+int cos_cache_attrval_compare(const void *e1, const void *e2)
+{
+ return slapi_utf8casecmp((unsigned char*)(*(cosAttrValue**)e1)->val,(unsigned char*)(*(cosAttrValue**)e2)->val);
+}
+#endif
+
+static int cos_cache_attr_compare(const void *e1, const void *e2)
+{
+ int com_Result;
+ cosAttributes *pAttr = (*(cosAttributes**)e1);
+ cosTemplates *pTemplate = (cosTemplates*)pAttr->pParent;
+ cosDefinitions *pDef = (cosDefinitions*)pTemplate->pParent;
+ cosAttrValue *pcostt = pDef->pCosTargetTree;
+ cosAttributes *pAttr1 = (*(cosAttributes**)e2);
+ cosTemplates *pTemplate1 = (cosTemplates*)pAttr1->pParent;
+ cosDefinitions *pDef1 = (cosDefinitions*)pTemplate1->pParent;
+ cosAttrValue *pcostt1 = pDef1->pCosTargetTree;
+
+ /* Now compare the names of the attributes */
+ com_Result = slapi_utf8casecmp((unsigned char*)(*(cosAttributes**)e1)->pAttrName,(unsigned char*)(*(cosAttributes**)e2)->pAttrName);
+ if(0 == com_Result)
+ /* Now compare the definition Dn parents */
+ com_Result = slapi_utf8casecmp((unsigned char*)pcostt1->val,(unsigned char*)pcostt->val);
+ if(0 == com_Result)
+ /* Now compare the cosPririoties */
+ com_Result = pTemplate->cosPriority - pTemplate1->cosPriority;
+ /* Now compare the prirority */
+ if(0 == com_Result)
+ return -1;
+ return com_Result;
+}
+
+#if 0
+int cos_cache_tmpl_compare(const void *e1, const void *e2)
+{
+ return slapi_utf8casecmp((unsigned char*)(*(cosTemplates**)e1)->cosGrade,(unsigned char*)(*(cosTemplates**)e2)->cosGrade);
+}
+#endif
+
+static int cos_cache_string_compare(const void *e1, const void *e2)
+{
+ return slapi_utf8casecmp((*(unsigned char**)e1),(*(unsigned char**)e2));
+}
+
+static int cos_cache_template_index_compare(const void *e1, const void *e2)
+{
+ int ret = 0;
+
+ if(0 == slapi_dn_issuffix((const char*)e1,*(const char**)e2))
+ ret = slapi_utf8casecmp(*(unsigned char**)e2,(unsigned char*)e1);
+ else
+ ret = 0;
+
+ return ret;
+}
+
+/*
+ cos_cache_template_index_bsearch
+ --------------------------------
+ searches the template dn index for a match
+*/
+static int cos_cache_template_index_bsearch(const char *dn)
+{
+ int ret = 0;
+ cosCache *pCache;
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_template_index_bsearch\n",0,0,0);
+
+ if(-1 != cos_cache_getref((cos_cache**)&pCache))
+ {
+ if(bsearch(dn, pCache->ppTemplateList, pCache->templateCount, sizeof(char*), cos_cache_template_index_compare))
+ ret = 1;
+
+ cos_cache_release((cos_cache*)pCache);
+ }
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_template_index_bsearch\n",0,0,0);
+
+ return ret;
+}
+
+/*
+ cos_cache_attr_index_bsearch - RECURSIVE
+ ----------------------------------------
+ performs a binary search on the cache attribute index
+ return -1 if key is not found
+ the index into attribute index array of the first occurrance
+ of that attribute type otherwise
+*/
+static int cos_cache_attr_index_bsearch( const cosCache *pCache, const cosAttributes *key, int lower, int upper )
+{
+ int ret = -1;
+ int index = 0;
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_attr_index_bsearch\n",0,0,0);
+
+ if(upper >= lower)
+ {
+ if(upper != 0)
+ index = ((upper-lower)/2) + lower;
+ else
+ index = 0;
+
+ ret = slapi_utf8casecmp((unsigned char*)key->pAttrName, (unsigned char*)(pCache->ppAttrIndex)[index]->pAttrName);
+ if(ret == 0)
+ {
+ /*
+ we have a match, backtrack to the
+ first occurrance of this attribute
+ type
+ */
+ do
+ {
+ index--;
+ if(index >= 0)
+ ret = slapi_utf8casecmp((unsigned char*)key->pAttrName, (unsigned char*)(pCache->ppAttrIndex)[index]->pAttrName);
+ } while(index >= 0 && ret == 0);
+
+ index++;
+ }
+ else
+ {
+ /* seek elsewhere */
+ if(ret < 0)
+ {
+ /* take the low road */
+ index = cos_cache_attr_index_bsearch(pCache, key, lower, index-1);
+ }
+ else
+ {
+ /* go high */
+ index = cos_cache_attr_index_bsearch(pCache, key, index+1, upper);
+ }
+ }
+ }
+ else
+ index = -1;
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_attr_index_bsearch\n",0,0,0);
+ return index;
+}
+
+
+static int cos_cache_cmp_attr(cosAttributes *pAttr, Slapi_Value *test_this, int *result)
+{
+ int ret = 0;
+ int index = 0;
+ cosAttrValue *pAttrVal = pAttr->pAttrValue;
+ char *the_cmp = (char *)slapi_value_get_string(test_this);
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_cmp_attr\n",0,0,0);
+
+ *result = 0;
+
+ while( pAttrVal )
+ {
+ if(!slapi_utf8casecmp((unsigned char*)the_cmp, (unsigned char*)pAttrVal->val))
+ {
+ /* compare match */
+ *result = 1;
+ break;
+ }
+
+ pAttrVal = pAttrVal->list.pNext;
+ index++;
+ }
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_cmp_attr\n",0,0,0);
+ return ret;
+}
+
+
+/*
+ cos_cache_cos_2_slapi_attr
+ ----------------------
+ converts a cosAttributes structure to a Slapi_Attribute
+*/
+static int cos_cache_cos_2_slapi_valueset(cosAttributes *pAttr, Slapi_ValueSet **out_vs)
+{
+ int ret = 0;
+ int index = 0;
+ cosAttrValue *pAttrVal = pAttr->pAttrValue;
+ int add_mode = 0;
+ static Slapi_Attr *attr = 0; /* allocated once, never freed */
+ static done_once = 0;
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_cos_2_slapi_attr\n",0,0,0);
+
+ /* test out_vs for existing values */
+ if(*out_vs)
+ {
+ add_mode = 1;
+ if(!done_once)
+ {
+ attr = slapi_attr_new(); /* lord knows why this is needed by slapi_valueset_find*/
+ slapi_attr_init(attr, "cos-bogus");
+ done_once = 1;
+ }
+ }
+ else
+ *out_vs = slapi_valueset_new();
+
+ if(*out_vs)
+ {
+ if(!add_mode)
+ slapi_valueset_init(*out_vs);
+
+ while( pAttrVal )
+ {
+ Slapi_Value *val = slapi_value_new_string(pAttrVal->val);
+ if(val) {
+ if(!add_mode || !slapi_valueset_find(attr, *out_vs, val)) {
+ slapi_valueset_add_value_ext(*out_vs, val, SLAPI_VALUE_FLAG_PASSIN);
+ }
+ else {
+ slapi_value_free(&val);
+ }
+ }
+ else
+ {
+ LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_cos_2_slapi_attr: memory allocation error\n",0,0,0);
+ ret = -1;
+ goto bail;
+ }
+
+ pAttrVal = pAttrVal->list.pNext;
+ index++;
+ }
+ }
+ else
+ {
+ LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_cos_2_slapi_attr: memory allocation error\n",0,0,0);
+ ret = -1;
+ }
+
+bail:
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_cos_2_slapi_attr\n",0,0,0);
+ return ret;
+}
+
+
+/*
+ cos_cache_change_notify
+ -----------------------
+ determines if the change effects the cache and if so
+ signals a rebuild.
+
+ XXXrbyrne This whole mechanism needs to be revisited--it means that
+ the modifying client gets his LDAP response, and an unspecified and
+ variable
+ period of time later, his mods get taken into account in the cos cache.
+ This makes it hard to program reliable admin tools for COS--DSAME
+ has already indicated this is an issue for them.
+ Additionally, it regenerates the _whole_ cache even for eeny weeny mods--
+ does it really neeed to ? Additionally, in order to ensure we
+ do not miss any mods, we may tend to regen the cache, even if we've already
+ taken a mod into account in an earlier regeneration--currently there is no
+ way to know we've already dealt with the mod.
+ The right thing is something like: figure out what's being changed
+ and change only that in the cos cache and do it _before_ the response
+ goes to the client....or do a task that he can poll.
+*/
+void cos_cache_change_notify(Slapi_PBlock *pb)
+{
+ char *dn;
+ Slapi_Attr *pObjclasses = 0;
+ int index = 0;
+ int do_update = 0;
+ struct slapi_entry *e;
+ Slapi_Backend *be=NULL;
+ int rc = 0;
+ int optype = -1;
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_change_notify\n",0,0,0);
+
+ /* Don't update local cache when remote entries */
+ /* are updated. */
+ slapi_pblock_get( pb, SLAPI_BACKEND, &be );
+ if ( ( be!=NULL ) && (slapi_be_is_flag_set(be,SLAPI_BE_FLAG_REMOTE_DATA)))
+ goto bail;
+
+ /* need to work out if a cache rebuild is necessary */
+ if(slapi_pblock_get( pb, SLAPI_TARGET_DN, &dn ))
+ {
+ LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_change_notify: failed to get dn of changed entry",0,0,0);
+ goto bail;
+ }
+
+ slapi_pblock_get(pb, SLAPI_PLUGIN_OPRETURN, &rc);
+ if (0 != rc) {
+ /* The operation did not succeed. As far as the cos cache is concerned, no need to update anything */
+ goto bail;
+ }
+
+ /*
+ * For DELETE, MODIFY, MODRDN: see if the pre-op entry was cos significant.
+ * For ADD, MODIFY, MODRDN: see if the post-op was cos significant.
+ * Touching a cos significant entry triggers the update
+ * of the whole cache.
+ */
+ slapi_pblock_get ( pb, SLAPI_OPERATION_TYPE, &optype );
+ if ( optype == SLAPI_OPERATION_DELETE ||
+ optype == SLAPI_OPERATION_MODIFY ||
+ optype == SLAPI_OPERATION_MODRDN ) {
+
+ slapi_pblock_get(pb, SLAPI_ENTRY_PRE_OP, &e);
+ if ( cos_cache_entry_is_cos_related(e)) {
+ do_update = 1;
+ }
+ }
+ if ( !do_update &&
+ (optype == SLAPI_OPERATION_ADD ||
+ optype == SLAPI_OPERATION_MODIFY ||
+ optype == SLAPI_OPERATION_MODRDN )) {
+
+ /* Adds have null pre-op entries */
+ slapi_pblock_get(pb, SLAPI_ENTRY_POST_OP, &e);
+ if ( cos_cache_entry_is_cos_related(e)) {
+ do_update = 1;
+ }
+ }
+
+ /*
+ * Check if this was an entry in a template tree (dn contains
+ * the old dn value).
+ * It's only relevant for indirect templates, which will
+ * not usually contain the "objectclass: costemplate" pair
+ * and so will not get detected by the above code.
+ * In fact, everything would still work fine if
+ * we just ignored a mod of one of these indirect templates,
+ * as we do not cache values from them, but the advantage of
+ * triggering an update here is that
+ * we can maintain the invariant that we only ever cache
+ * definitions that have _valid_ templates--the active cache
+ * stays lean in the face of errors.
+ */
+ if( !do_update && cos_cache_template_index_bsearch(dn)) {
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "cos_cache_change_notify:"
+ "updating due to indirect template change(%s)\n",
+ dn,0,0);
+ do_update = 1;
+ }
+
+ /* Do the update if required */
+ if(do_update)
+ {
+ slapi_lock_mutex(change_lock);
+ slapi_notify_condvar( something_changed, 1 );
+ cos_cache_notify_flag = 1;
+ slapi_unlock_mutex(change_lock);
+ }
+
+bail:
+ LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_change_notify\n",0,0,0);
+}
+
+/*
+ cos_cache_stop
+ --------------
+ notifies the cache thread we are stopping
+*/
+void cos_cache_stop()
+{
+ LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_stop\n",0,0,0);
+
+ /* first deregister our state change func */
+ slapi_unregister_backend_state_change((void *)cos_cache_backend_state_change);
+
+ slapi_lock_mutex(change_lock);
+ keeprunning = 0;
+ slapi_notify_condvar( something_changed, 1 );
+ slapi_unlock_mutex(change_lock);
+
+ /* wait on shutdown */
+ slapi_lock_mutex(stop_lock);
+
+ /* release the caches reference to the cache */
+ cos_cache_release(pCache);
+
+ slapi_destroy_mutex(cache_lock);
+ slapi_destroy_mutex(change_lock);
+ slapi_destroy_condvar(something_changed);
+
+ slapi_unlock_mutex(stop_lock);
+ slapi_destroy_mutex(stop_lock);
+ slapi_destroy_condvar(start_cond);
+ slapi_destroy_mutex(start_lock);
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_stop\n",0,0,0);
+}
+
+/*
+ cos_cache_backwards_stricmp_and_clip
+ ------------------------------------
+ compares s2 to s1 starting from end of the strings until the beginning of
+ either matches result in the s2 value being clipped from s1 with a NULL char
+ and 1 being returned as opposed to 0
+
+*/
+static int cos_cache_backwards_stricmp_and_clip(char*s1,char*s2)
+{
+ int ret = 0;
+ int s1len = 0;
+ int s2len = 0;
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "--> cos_cache_backwards_stricmp_and_clip\n",0,0,0);
+
+ s1len = strlen(s1);
+ s2len = strlen(s2);
+
+ if(s1len > s2len && s2len > 0)
+ {
+ while(s1len > -1 && s2len > -1)
+ {
+ s1len--;
+ s2len--;
+
+ if(s1[s1len] != s2[s2len])
+ break;
+ else
+ {
+ if(s2len == 0)
+ {
+ /* hit! now clip */
+ ret = 1;
+ s1[s1len] = '\0';
+ }
+ }
+ }
+ }
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "<-- cos_cache_backwards_stricmp_and_clip\n",0,0,0);
+ return ret;
+}
+
+
+static int cos_cache_follow_pointer( vattr_context *c, const char *dn, char *type, Slapi_ValueSet **out_vs, Slapi_Value *test_this, int *result, int flags)
+{
+ int ret = -1; /* assume failure */
+ Slapi_PBlock *pDnSearch = 0;
+ Slapi_Entry **pEntryList = 0;
+ char *attrs[2];
+ int entryIndex = 0;
+ int op = 0;
+ int type_test = 0;
+ int type_name_disposition = 0;
+ char *actual_type_name = 0;
+ int free_flags = 0;
+ Slapi_ValueSet *tmp_vs = 0;
+
+ attrs[0] = type;
+ attrs[1] = 0;
+
+ /* Use new internal operation API */
+ pDnSearch = slapi_pblock_new();
+ if (pDnSearch) {
+ slapi_search_internal_set_pb(pDnSearch, dn, LDAP_SCOPE_BASE,"(|(objectclass=*)(objectclass=ldapsubentry))",attrs,
+ 0,NULL,NULL,cos_get_plugin_identity(),0);
+ slapi_search_internal_pb(pDnSearch);
+ slapi_pblock_get( pDnSearch, SLAPI_PLUGIN_INTOP_RESULT, &ret);
+ }
+
+ if(pDnSearch && (ret == LDAP_SUCCESS))
+ {
+ ret = -1;
+
+ slapi_pblock_get( pDnSearch, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &pEntryList);
+ if(pEntryList)
+ {
+ if(out_vs) /* if this set, a value is required */
+ op = 1;
+ else if(test_this && result) /* compare op */
+ op = 2;
+ else
+ {
+ /* requires only type present */
+ op = 1;
+ type_test = 1;
+ }
+
+ switch(op)
+ {
+ case 1:
+ /* straight value return or type test */
+ if(type_test)
+ out_vs = &tmp_vs;
+
+ ret = slapi_vattr_values_get_sp(c, pEntryList[0], type, out_vs,&type_name_disposition, &actual_type_name, flags, &free_flags);
+
+ if(actual_type_name)
+ slapi_ch_free((void **) &actual_type_name);
+
+ if(type_test && free_flags == SLAPI_VIRTUALATTRS_RETURNED_COPIES)
+ slapi_valueset_free(*out_vs);
+
+ break;
+
+ case 2:
+ /* this must be a compare op */
+ ret = slapi_vattr_value_compare_sp(c, pEntryList[0],type, test_this, result, flags);
+ break;
+
+ default:
+ goto bail;
+ }
+ }
+ }
+
+bail:
+ /* clean up */
+ if(pDnSearch)
+ {
+ slapi_free_search_results_internal(pDnSearch);
+ slapi_pblock_destroy(pDnSearch);
+ }
+
+ return ret;
+}
+
+
+/*
+ * cos_cache_backend_state_change()
+ * --------------------------------
+ * This is called when a backend changes state
+ * We simply signal to rebuild the cos cache in this case
+ *
+ */
+void cos_cache_backend_state_change(void *handle, char *be_name,
+ int old_be_state, int new_be_state)
+{
+ slapi_lock_mutex(change_lock);
+ slapi_notify_condvar( something_changed, 1 );
+ slapi_unlock_mutex(change_lock);
+}
+
+/*
+ * returns non-zero: entry is cos significant (note does not detect indirect
+ * template entries).
+ * 0 : entry is not cos significant.
+*/
+static int cos_cache_entry_is_cos_related( Slapi_Entry *e) {
+
+ int rc = 0;
+ Slapi_Attr *pObjclasses = NULL;
+
+ if ( e == NULL ) {
+ LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_change_notify:"
+ "modified entry is NULL--updating cache just in case!",
+ 0,0,0);
+ rc = 1;
+ } else {
+
+ if(slapi_entry_attr_find( e, "objectclass", &pObjclasses ))
+ {
+ LDAPDebug( LDAP_DEBUG_ANY, "cos_cache_change_notify:"
+ " failed to get objectclass from %s",
+ slapi_entry_get_dn(e),0,0);
+ rc = 0;
+ } else {
+
+ Slapi_Value *val = NULL;
+ int index = 0;
+ char *pObj;
+
+ /* check out the object classes to see if this was a cosDefinition */
+
+ index = slapi_attr_first_value( pObjclasses, &val );
+ while(!rc && val)
+ {
+ pObj = (char*)slapi_value_get_string(val);
+
+ /*
+ * objectclasses are ascii--maybe strcasecmp() is faster than
+ * slapi_utf8casecmp()
+ */
+ if( !strcasecmp(pObj, "cosdefinition") ||
+ !strcasecmp(pObj, "cossuperdefinition") ||
+ !strcasecmp(pObj, "costemplate")
+ )
+ {
+ rc = 1;
+ }
+
+ index = slapi_attr_next_value( pObjclasses, index, &val );
+ }
+ }
+ }
+ return(rc);
+}
diff --git a/ldap/servers/plugins/cos/cos_cache.h b/ldap/servers/plugins/cos/cos_cache.h
new file mode 100644
index 00000000..ef47a9ab
--- /dev/null
+++ b/ldap/servers/plugins/cos/cos_cache.h
@@ -0,0 +1,19 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#if !defined( _COS_CACHE_H )
+#define _COS_CACHE_H
+
+typedef void cos_cache;
+
+int cos_cache_init();
+void cos_cache_stop();
+int cos_cache_getref(cos_cache **ppCache);
+int cos_cache_addref(cos_cache *pCache);
+int cos_cache_release(cos_cache *pCache);
+void cos_cache_change_notify(Slapi_PBlock *pb);
+
+#endif /* _COS_CACHE_H */
diff --git a/ldap/servers/plugins/cos/dllmain.c b/ldap/servers/plugins/cos/dllmain.c
new file mode 100644
index 00000000..fabf8677
--- /dev/null
+++ b/ldap/servers/plugins/cos/dllmain.c
@@ -0,0 +1,96 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/*
+ * Microsoft Windows specifics for BACK-LDBM DLL
+ */
+#include "ldap.h"
+#include "lber.h"
+
+
+#ifdef _WIN32
+/* Lifted from Q125688
+ * How to Port a 16-bit DLL to a Win32 DLL
+ * on the MSVC 4.0 CD
+ */
+BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved)
+{
+ WSADATA wsadata;
+
+ switch (fdwReason)
+ {
+ case DLL_PROCESS_ATTACH:
+ /* Code from LibMain inserted here. Return TRUE to keep the
+ DLL loaded or return FALSE to fail loading the DLL.
+
+ You may have to modify the code in your original LibMain to
+ account for the fact that it may be called more than once.
+ You will get one DLL_PROCESS_ATTACH for each process that
+ loads the DLL. This is different from LibMain which gets
+ called only once when the DLL is loaded. The only time this
+ is critical is when you are using shared data sections.
+ If you are using shared data sections for statically
+ allocated data, you will need to be careful to initialize it
+ only once. Check your code carefully.
+
+ Certain one-time initializations may now need to be done for
+ each process that attaches. You may also not need code from
+ your original LibMain because the operating system may now
+ be doing it for you.
+ */
+ /*
+ * 16 bit code calls UnlockData()
+ * which is mapped to UnlockSegment in windows.h
+ * in 32 bit world UnlockData is not defined anywhere
+ * UnlockSegment is mapped to GlobalUnfix in winbase.h
+ * and the docs for both UnlockSegment and GlobalUnfix say
+ * ".. function is oboslete. Segments have no meaning
+ * in the 32-bit environment". So we do nothing here.
+ */
+
+ if( errno = WSAStartup(0x0101, &wsadata ) != 0 )
+ return FALSE;
+
+ break;
+
+ case DLL_THREAD_ATTACH:
+ /* Called each time a thread is created in a process that has
+ already loaded (attached to) this DLL. Does not get called
+ for each thread that exists in the process before it loaded
+ the DLL.
+
+ Do thread-specific initialization here.
+ */
+ break;
+
+ case DLL_THREAD_DETACH:
+ /* Same as above, but called when a thread in the process
+ exits.
+
+ Do thread-specific cleanup here.
+ */
+ break;
+
+ case DLL_PROCESS_DETACH:
+ /* Code from _WEP inserted here. This code may (like the
+ LibMain) not be necessary. Check to make certain that the
+ operating system is not doing it for you.
+ */
+ WSACleanup();
+
+ break;
+ }
+ /* The return value is only used for DLL_PROCESS_ATTACH; all other
+ conditions are ignored. */
+ return TRUE; // successful DLL_PROCESS_ATTACH
+}
+#else
+int CALLBACK
+LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine )
+{
+ /*UnlockData( 0 );*/
+ return( 1 );
+}
+#endif
diff --git a/ldap/servers/plugins/distrib/Makefile b/ldap/servers/plugins/distrib/Makefile
new file mode 100644
index 00000000..3144e8cb
--- /dev/null
+++ b/ldap/servers/plugins/distrib/Makefile
@@ -0,0 +1,109 @@
+#
+# PROPRIETARY/CONFIDENTIAL. Use of this product is subject to
+# license terms. Copyright 2001 Sun Microsystems, Inc.
+# Some preexisting portions Copyright 2001 Netscape Communications Corp.
+# All rights reserved.
+#
+#
+# GNU Makefile for Directory Server distribution plugin
+#
+
+LDAP_SRC = ../../..
+MCOM_ROOT = ../../../../..
+
+NOSTDCLEAN=true # don't let nsconfig.mk define target clean
+NOSTDSTRIP=true # don't let nsconfig.mk define target strip
+NSPR20=true # probably should be defined somewhere else (not sure where)
+
+OBJDEST = $(OBJDIR)/lib/libdistrib
+LIBDIR = $(LIB_RELDIR)
+
+include $(MCOM_ROOT)/ldapserver/nsconfig.mk
+include $(LDAP_SRC)/nsldap.mk
+
+ifeq ($(ARCH), WINNT)
+DEF_FILE:=./libdistrib.def
+endif
+
+CFLAGS+=$(SLCFLAGS)
+
+INCLUDES += -I$(LDAP_SRC)/servers/slapd
+
+DIS_OBJS= \
+ distrib.o
+
+
+OBJS = $(addprefix $(OBJDEST)/, $(DIS_OBJS))
+
+ifeq ($(ARCH), WINNT)
+LIBDIS_DLL_OBJ = $(addprefix $(OBJDEST)/, dllmain.o)
+endif
+
+# The sample distribution plugin is not part of DS.
+# So we generate the shared library outside of $(LIBDIR)
+# so that it's not retreived by the packaging makefiles.
+#LIBDIS = $(addprefix $(LIBDIR)/, $(DIS_DLL).$(DLL_SUFFIX))
+LIBDIS = $(addprefix $(OBJDEST)/, $(DIS_DLL).$(DLL_SUFFIX))
+
+ifeq ($(ARCH), WINNT)
+EXTRA_LIBS_DEP += \
+ $(LIBSLAPD_DEP) \
+ $(LDAP_LIBUTIL_DEP) \
+ $(LDAP_COMMON_LIBS_DEP)
+EXTRA_LIBS_DEP += \
+ $(LDAPSDK_DEP) \
+ $(SECURITY_DEP)
+EXTRA_LIBS += \
+ $(LIBSLAPD) \
+ $(LDAP_SDK_LIBLDAP_DLL) \
+ $(LIBUTIL) \
+ $(NSPRLINK) \
+ $(LDAP_COMMON_LIBS)
+endif
+ifeq ($(ARCH), AIX)
+EXTRA_LIBS_DEP += \
+ $(LIBSLAPD_DEP) \
+ $(LDAP_LIBUTIL_DEP) \
+ $(LDAP_COMMON_LIBS_DEP)
+EXTRA_LIBS_DEP += \
+ $(LDAPSDK_DEP)
+EXTRA_LIBS += \
+ $(LIBSLAPDLINK) \
+ $(LDAP_SDK_LIBLDAP_DLL) \
+ $(LIBUTIL) \
+ $(NSPRLINK) \
+ $(LDAP_COMMON_LIBS)
+endif
+
+ifeq ($(ARCH), HPUX)
+EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAPSDK_DEP) $(NSPR_DEP)
+EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(NSPRLINK) $(ICULINK)
+endif
+
+ifeq ($(ARCH), WINNT)
+DLL_LDFLAGS += -def:"./libdistrib.def"
+CFLAGS+= /WX
+endif # WINNT
+
+ifeq ($(ARCH), AIX)
+LD=ld
+endif
+
+clientSDK:
+
+all: $(OBJDEST) $(LIBDIR) $(LIBDIS)
+
+$(LIBDIS): $(OBJS) $(LIBDIS_DLL_OBJ) $(DEF_FILE)
+ $(LINK_DLL) $(LIBDIS_DLL_OBJ) $(EXTRA_LIBS)
+
+veryclean: clean
+
+clean:
+ $(RM) $(OBJS)
+ifeq ($(ARCH), WINNT)
+ $(RM) $(LIBDIS_DLL_OBJ)
+endif
+ $(RM) $(LIBDIS)
+
+$(OBJDEST):
+ $(MKDIR) $(OBJDEST)
diff --git a/ldap/servers/plugins/distrib/Makefile.AIX b/ldap/servers/plugins/distrib/Makefile.AIX
new file mode 100644
index 00000000..d155626d
--- /dev/null
+++ b/ldap/servers/plugins/distrib/Makefile.AIX
@@ -0,0 +1,44 @@
+#
+# PROPRIETARY/CONFIDENTIAL. Use of this product is subject to
+# license terms. Copyright 2001 Sun Microsystems, Inc.
+# Some preexisting portions Copyright 2001 Netscape Communications Corp.
+# All rights reserved.
+#
+# AIX Makefile for Directory Server plug-in examples
+# NOTE: Make sure to set the DSLIB variable to the path
+# to the libslapd_shr.a file (for example,
+# DSLIB = /usr/netscape/suitespot/lib/libslapd_shr.a
+
+CC = xlC_r
+LD = ld
+
+# Set this to the path to the libslapd_shr.a file
+DSLIB =
+
+INCLUDE_FLAGS= -I../../include
+CFLAGS= $(INCLUDE_FLAGS) -qarch=com
+LIBPATH=/usr/lib/threads:/usr/lpp/xlC/lib:/usr/lib:/lib:..:../../../../lib
+EXTRA_LIBS= -bI:/usr/lib/lowsys.exp -lC_r -lC -lpthreads -lc_r -lm \
+ /usr/lib/libc.a $(DSLIB)
+LDFLAGS= -bE:libtest-plugin_shr.exp -bM:SRE -bnoentry -blibpath:$(LIBPATH) \
+ $(EXTRA_LIBS)
+
+OBJS = distrib
+
+all: libtest-plugin_shr.a
+
+
+libtest-plugin_shr.a: $(OBJS)
+ rm -f libtest-plugin_shr.exp
+ echo "#!" > libtest-plugin_shr.exp
+ nm -B -C -g $(OBJS) | \
+ awk '/ [B,T,D] / {print $$3}' | \
+ sed -e 's/^\.//' | sort -u >> libtest-plugin_shr.exp
+ $(LD) $(LDFLAGS) -o $@ $(OBJS)
+
+.c.o:
+ $(CC) $(CFLAGS) -c $<
+
+clean:
+ -rm -f $(OBJS) libtest-plugin_shr.a
+
diff --git a/ldap/servers/plugins/distrib/Makefile.BSDI b/ldap/servers/plugins/distrib/Makefile.BSDI
new file mode 100644
index 00000000..c8016c9e
--- /dev/null
+++ b/ldap/servers/plugins/distrib/Makefile.BSDI
@@ -0,0 +1,30 @@
+#
+# PROPRIETARY/CONFIDENTIAL. Use of this product is subject to
+# license terms. Copyright 2001 Sun Microsystems, Inc.
+# Some preexisting portions Copyright 2001 Netscape Communications Corp.
+# All rights reserved.
+#
+# SOLARIS Makefile for Directory Server plug-in examples
+#
+
+CC = cc
+LD = ld
+
+INCLUDE_FLAGS = -I../../include
+CFLAGS = $(INCLUDE_FLAGS) -D_REENTRANT -KPIC
+LDFLAGS = -G
+
+OBJS = distrib.o
+
+all: libtest-plugin.so
+
+
+libtest-plugin.so: $(OBJS)
+ $(LD) $(LDFLAGS) -o $@ $(OBJS)
+
+.c.o:
+ $(CC) $(CFLAGS) -c $<
+
+clean:
+ -rm -f $(OBJS) libtest-plugin.so
+
diff --git a/ldap/servers/plugins/distrib/Makefile.HPUX b/ldap/servers/plugins/distrib/Makefile.HPUX
new file mode 100644
index 00000000..34c24521
--- /dev/null
+++ b/ldap/servers/plugins/distrib/Makefile.HPUX
@@ -0,0 +1,27 @@
+#
+# PROPRIETARY/CONFIDENTIAL. Use of this product is subject to
+# license terms. Copyright 2001 Sun Microsystems, Inc.
+# Some preexisting portions Copyright 2001 Netscape Communications Corp.
+# All rights reserved.
+#
+# HPUX Makefile for Directory Server plug-in examples
+
+CC = cc
+LD = ld
+
+INCLUDE = -I../../include
+CFLAGS=$(INCLUDE) -D_HPUX_SOURCE -Aa +DA1.0 +z
+LDFLAGS = -b
+
+OBJS = distrib.o
+
+all: libtest-plugin.sl
+
+libtest-plugin.sl: $(OBJS)
+ $(LD) $(LDFLAGS) -o $@ $(OBJS)
+
+.c.o:
+ $(CC) $(CFLAGS) -c $<
+
+clean:
+ -rm -f $(OBJS) libtest-plugin.sl
diff --git a/ldap/servers/plugins/distrib/Makefile.HPUX64 b/ldap/servers/plugins/distrib/Makefile.HPUX64
new file mode 100644
index 00000000..d6a09339
--- /dev/null
+++ b/ldap/servers/plugins/distrib/Makefile.HPUX64
@@ -0,0 +1,27 @@
+#
+# PROPRIETARY/CONFIDENTIAL. Use of this product is subject to
+# license terms. Copyright 2001 Sun Microsystems, Inc.
+# Some preexisting portions Copyright 2001 Netscape Communications Corp.
+# All rights reserved.
+#
+# HPUX Makefile for Directory Server plug-in examples
+
+CC = cc
+LD = ld
+
+INCLUDE = -I../../include
+CFLAGS=$(INCLUDE) -D_HPUX_SOURCE -Aa +DA2.0W +z
+LDFLAGS = -b
+
+OBJS = distrib.o
+
+all: libtest-plugin.sl
+
+libtest-plugin.sl: $(OBJS)
+ $(LD) $(LDFLAGS) -o $@ $(OBJS)
+
+.c.o:
+ $(CC) $(CFLAGS) -c $<
+
+clean:
+ -rm -f $(OBJS) libtest-plugin.sl
diff --git a/ldap/servers/plugins/distrib/Makefile.IRIX b/ldap/servers/plugins/distrib/Makefile.IRIX
new file mode 100644
index 00000000..f2ffc0c7
--- /dev/null
+++ b/ldap/servers/plugins/distrib/Makefile.IRIX
@@ -0,0 +1,30 @@
+#
+# PROPRIETARY/CONFIDENTIAL. Use of this product is subject to
+# license terms. Copyright 2001 Sun Microsystems, Inc.
+# Some preexisting portions Copyright 2001 Netscape Communications Corp.
+# All rights reserved.
+#
+# IRIX Makefile for Directory Server plug-in examples
+#
+
+CC = cc
+LD = ld
+
+INCLUDE_FLAGS = -I../../include
+CFLAGS = $(INCLUDE_FLAGS) -D_SGI_MP_SOURCE -fullwarn
+LDFLAGS = -32 -shared
+
+OBJS = distrib.o
+
+all: libtest-plugin.so
+
+
+libtest-plugin.so: $(OBJS)
+ $(LD) $(LDFLAGS) -o $@ $(OBJS)
+
+.c.o:
+ $(CC) $(CFLAGS) -c $<
+
+clean:
+ -rm -f $(OBJS) libtest-plugin.so
+
diff --git a/ldap/servers/plugins/distrib/Makefile.Linux b/ldap/servers/plugins/distrib/Makefile.Linux
new file mode 100644
index 00000000..b5fe839f
--- /dev/null
+++ b/ldap/servers/plugins/distrib/Makefile.Linux
@@ -0,0 +1,30 @@
+#
+# PROPRIETARY/CONFIDENTIAL. Use of this product is subject to
+# license terms. Copyright 2001 Sun Microsystems, Inc.
+# Some preexisting portions Copyright 2001 Netscape Communications Corp.
+# All rights reserved.
+#
+# SOLARIS Makefile for Directory Server plug-in examples
+#
+
+CC = gcc
+LD = ld
+
+INCLUDE_FLAGS = -I../../include
+CFLAGS = $(INCLUDE_FLAGS) -D_REENTRANT -KPIC
+LDFLAGS = -G
+
+OBJS = distrib.o
+
+all: libtest-plugin.so
+
+
+libtest-plugin.so: $(OBJS)
+ $(LD) $(LDFLAGS) -o $@ $(OBJS)
+
+.c.o:
+ $(CC) $(CFLAGS) -c $<
+
+clean:
+ -rm -f $(OBJS) libtest-plugin.so
+
diff --git a/ldap/servers/plugins/distrib/Makefile.OSF1 b/ldap/servers/plugins/distrib/Makefile.OSF1
new file mode 100644
index 00000000..2c2c4660
--- /dev/null
+++ b/ldap/servers/plugins/distrib/Makefile.OSF1
@@ -0,0 +1,29 @@
+#
+# PROPRIETARY/CONFIDENTIAL. Use of this product is subject to
+# license terms. Copyright 2001 Sun Microsystems, Inc.
+# Some preexisting portions Copyright 2001 Netscape Communications Corp.
+# All rights reserved.
+#
+# OSF1 Makefile for Directory Server plug-in examples
+
+CC = cc
+LD = ld
+
+INCLUDE = -I../../include
+CFLAGS = $(INCLUDE) -DIS_64 -ieee_with_inexact -pthread -DOSF1
+LDFLAGS = -shared -all -expect_unresolved "*" -taso
+
+
+OBJS = distrib.o
+
+all: libtest-plugin.so
+
+libtest-plugin.so: $(OBJS)
+ $(LD) $(LDFLAGS) -o $@ $(OBJS)
+
+.c.o:
+ $(CC) $(CFLAGS) -c $<
+
+clean:
+ -rm -f $(OBJS) libtest-plugin.so
+
diff --git a/ldap/servers/plugins/distrib/Makefile.ReliantUNIX b/ldap/servers/plugins/distrib/Makefile.ReliantUNIX
new file mode 100644
index 00000000..c8016c9e
--- /dev/null
+++ b/ldap/servers/plugins/distrib/Makefile.ReliantUNIX
@@ -0,0 +1,30 @@
+#
+# PROPRIETARY/CONFIDENTIAL. Use of this product is subject to
+# license terms. Copyright 2001 Sun Microsystems, Inc.
+# Some preexisting portions Copyright 2001 Netscape Communications Corp.
+# All rights reserved.
+#
+# SOLARIS Makefile for Directory Server plug-in examples
+#
+
+CC = cc
+LD = ld
+
+INCLUDE_FLAGS = -I../../include
+CFLAGS = $(INCLUDE_FLAGS) -D_REENTRANT -KPIC
+LDFLAGS = -G
+
+OBJS = distrib.o
+
+all: libtest-plugin.so
+
+
+libtest-plugin.so: $(OBJS)
+ $(LD) $(LDFLAGS) -o $@ $(OBJS)
+
+.c.o:
+ $(CC) $(CFLAGS) -c $<
+
+clean:
+ -rm -f $(OBJS) libtest-plugin.so
+
diff --git a/ldap/servers/plugins/distrib/Makefile.SOLARIS b/ldap/servers/plugins/distrib/Makefile.SOLARIS
new file mode 100644
index 00000000..c8016c9e
--- /dev/null
+++ b/ldap/servers/plugins/distrib/Makefile.SOLARIS
@@ -0,0 +1,30 @@
+#
+# PROPRIETARY/CONFIDENTIAL. Use of this product is subject to
+# license terms. Copyright 2001 Sun Microsystems, Inc.
+# Some preexisting portions Copyright 2001 Netscape Communications Corp.
+# All rights reserved.
+#
+# SOLARIS Makefile for Directory Server plug-in examples
+#
+
+CC = cc
+LD = ld
+
+INCLUDE_FLAGS = -I../../include
+CFLAGS = $(INCLUDE_FLAGS) -D_REENTRANT -KPIC
+LDFLAGS = -G
+
+OBJS = distrib.o
+
+all: libtest-plugin.so
+
+
+libtest-plugin.so: $(OBJS)
+ $(LD) $(LDFLAGS) -o $@ $(OBJS)
+
+.c.o:
+ $(CC) $(CFLAGS) -c $<
+
+clean:
+ -rm -f $(OBJS) libtest-plugin.so
+
diff --git a/ldap/servers/plugins/distrib/Makefile.SOLARIS64 b/ldap/servers/plugins/distrib/Makefile.SOLARIS64
new file mode 100644
index 00000000..170d2b47
--- /dev/null
+++ b/ldap/servers/plugins/distrib/Makefile.SOLARIS64
@@ -0,0 +1,30 @@
+#
+# PROPRIETARY/CONFIDENTIAL. Use of this product is subject to
+# license terms. Copyright 2001 Sun Microsystems, Inc.
+# Some preexisting portions Copyright 2001 Netscape Communications Corp.
+# All rights reserved.
+#
+# SOLARIS Makefile for Directory Server plug-in examples
+#
+
+CC = cc
+LD = ld
+
+INCLUDE_FLAGS = -I../../include
+CFLAGS = $(INCLUDE_FLAGS) -D_REENTRANT -KPIC -xarch=v9
+LDFLAGS = -G -xarch=v9
+
+OBJS = distrib.o
+
+all: libtest-plugin.so
+
+
+libtest-plugin.so: $(OBJS)
+ $(LD) $(LDFLAGS) -o $@ $(OBJS)
+
+.c.o:
+ $(CC) $(CFLAGS) -c $<
+
+clean:
+ -rm -f $(OBJS) libtest-plugin.so
+
diff --git a/ldap/servers/plugins/distrib/Makefile.SOLARISx86 b/ldap/servers/plugins/distrib/Makefile.SOLARISx86
new file mode 100644
index 00000000..c8016c9e
--- /dev/null
+++ b/ldap/servers/plugins/distrib/Makefile.SOLARISx86
@@ -0,0 +1,30 @@
+#
+# PROPRIETARY/CONFIDENTIAL. Use of this product is subject to
+# license terms. Copyright 2001 Sun Microsystems, Inc.
+# Some preexisting portions Copyright 2001 Netscape Communications Corp.
+# All rights reserved.
+#
+# SOLARIS Makefile for Directory Server plug-in examples
+#
+
+CC = cc
+LD = ld
+
+INCLUDE_FLAGS = -I../../include
+CFLAGS = $(INCLUDE_FLAGS) -D_REENTRANT -KPIC
+LDFLAGS = -G
+
+OBJS = distrib.o
+
+all: libtest-plugin.so
+
+
+libtest-plugin.so: $(OBJS)
+ $(LD) $(LDFLAGS) -o $@ $(OBJS)
+
+.c.o:
+ $(CC) $(CFLAGS) -c $<
+
+clean:
+ -rm -f $(OBJS) libtest-plugin.so
+
diff --git a/ldap/servers/plugins/distrib/Makefile.UnixWare b/ldap/servers/plugins/distrib/Makefile.UnixWare
new file mode 100644
index 00000000..c8016c9e
--- /dev/null
+++ b/ldap/servers/plugins/distrib/Makefile.UnixWare
@@ -0,0 +1,30 @@
+#
+# PROPRIETARY/CONFIDENTIAL. Use of this product is subject to
+# license terms. Copyright 2001 Sun Microsystems, Inc.
+# Some preexisting portions Copyright 2001 Netscape Communications Corp.
+# All rights reserved.
+#
+# SOLARIS Makefile for Directory Server plug-in examples
+#
+
+CC = cc
+LD = ld
+
+INCLUDE_FLAGS = -I../../include
+CFLAGS = $(INCLUDE_FLAGS) -D_REENTRANT -KPIC
+LDFLAGS = -G
+
+OBJS = distrib.o
+
+all: libtest-plugin.so
+
+
+libtest-plugin.so: $(OBJS)
+ $(LD) $(LDFLAGS) -o $@ $(OBJS)
+
+.c.o:
+ $(CC) $(CFLAGS) -c $<
+
+clean:
+ -rm -f $(OBJS) libtest-plugin.so
+
diff --git a/ldap/servers/plugins/distrib/Makefile.UnixWareUDK b/ldap/servers/plugins/distrib/Makefile.UnixWareUDK
new file mode 100644
index 00000000..c8016c9e
--- /dev/null
+++ b/ldap/servers/plugins/distrib/Makefile.UnixWareUDK
@@ -0,0 +1,30 @@
+#
+# PROPRIETARY/CONFIDENTIAL. Use of this product is subject to
+# license terms. Copyright 2001 Sun Microsystems, Inc.
+# Some preexisting portions Copyright 2001 Netscape Communications Corp.
+# All rights reserved.
+#
+# SOLARIS Makefile for Directory Server plug-in examples
+#
+
+CC = cc
+LD = ld
+
+INCLUDE_FLAGS = -I../../include
+CFLAGS = $(INCLUDE_FLAGS) -D_REENTRANT -KPIC
+LDFLAGS = -G
+
+OBJS = distrib.o
+
+all: libtest-plugin.so
+
+
+libtest-plugin.so: $(OBJS)
+ $(LD) $(LDFLAGS) -o $@ $(OBJS)
+
+.c.o:
+ $(CC) $(CFLAGS) -c $<
+
+clean:
+ -rm -f $(OBJS) libtest-plugin.so
+
diff --git a/ldap/servers/plugins/distrib/Makefile.WINNT b/ldap/servers/plugins/distrib/Makefile.WINNT
new file mode 100644
index 00000000..ddb02e29
--- /dev/null
+++ b/ldap/servers/plugins/distrib/Makefile.WINNT
@@ -0,0 +1,38 @@
+# Makefile for Directory Server plug-in
+#
+
+CC = cl
+LD = link
+
+
+TARGET=libdistrib
+
+OBJS=distrib.obj
+
+
+INC = ..\..\include
+CFLAGS = /nologo -I $(INC) /c
+LDFLAGS = /dll /nologo
+LIBS=/DEFAULTLIB:kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib wsock32.lib ..\..\lib\libslapd.lib ..\..\lib\libnspr4.lib
+
+
+all: \
+ init \
+ $(TARGET).dll
+
+init:
+ "c:\program files\microsoft visual studio\vc98\bin\vcvars32.bat"
+
+
+$(TARGET).dll: $(OBJS)
+ $(LD) $(LDFLAGS) /def:$(TARGET).def /out:$(TARGET).dll $(LIBS) $(OBJS)
+ -rm -f $(OBJS2) *~
+
+.c.obj:
+ $(CC) $(CFLAGS) $<
+
+clean:
+ del -f $(OBJS) $(TARGET).dll *~
+
+
+
diff --git a/ldap/servers/plugins/distrib/README b/ldap/servers/plugins/distrib/README
new file mode 100644
index 00000000..3ee6ff88
--- /dev/null
+++ b/ldap/servers/plugins/distrib/README
@@ -0,0 +1,23 @@
+ ----------------------------
+ Sample pluggable distribution logic
+ for Netscape Directory Server
+ ----------------------------
+
+This directory contains code for some sample server plug-ins intended for
+use with the Netscape Directory Server 7.
+
+ NOTE: Before you compile and run these examples, make sure
+ to change any server-specific data in the examples to
+ values applicable to your Directory Server.
+
+distrib.c
+----------
+This is an example of a distribution function that can be used
+to distribute a flat namespace into several backends
+
+/**
+ * PROPRIETARY/CONFIDENTIAL. Use of this product is subject to
+ * license terms. Copyright 2001 Sun Microsystems, Inc.
+ * Some preexisting portions Copyright 2001 Netscape Communications Corp.
+ * All rights reserved.
+ */
diff --git a/ldap/servers/plugins/distrib/distrib.c b/ldap/servers/plugins/distrib/distrib.c
new file mode 100644
index 00000000..fd8ea5b6
--- /dev/null
+++ b/ldap/servers/plugins/distrib/distrib.c
@@ -0,0 +1,222 @@
+/**
+ * PROPRIETARY/CONFIDENTIAL. Use of this product is subject to
+ * license terms. Copyright 2001 Sun Microsystems, Inc.
+ * Some preexisting portions Copyright 2001 Netscape Communications Corp.
+ * All rights reserved.
+ */
+#include <ctype.h>
+#include <string.h>
+#include "slapi-plugin.h"
+
+/*
+ * These are examples of distribution function as declared in mapping tree node
+ * This function will be called for every operations
+ * reaching this node, including subtree search operations that are started
+ * above this node
+ *
+ * Parameters :
+ * . pb is the pblock of the operation
+ * . target_dn is the target DN of the operation
+ * . mtn_be_names is the list of names of backends declared for this node
+ * . be_count is the number of backends declared
+ * . node_dn is the node where the distribution function is set
+ *
+ * The return value of the functions should be the indice of the backend
+ * in the mtn_be_names table
+ * For search operation, the value SLAPI_BE_ALL_BACKENDS can be used to
+ * specify that all backends must be searched
+ * The use of value SLAPI_BE_ALL_BACKENDS for operation other than search
+ * is not supported and may give random results
+ *
+ */
+
+/*
+ * Distribute the entries based on the first letter of their rdn
+ *
+ * . Entries starting with anything other that a-z or A-Z will always
+ * go in backend 0
+ * . Entries starting with letter (a-z or A-Z) will be shared between
+ * the backends depending following the alphabetic order
+ * Example : if 3 backends are used, entries starting with A-I will go
+ * in backend 0, entries starting with J-R will go in backend 1, entries
+ * starting with S-Z will go in backend 2
+ *
+ * Of course this won't work for all locales...
+ *
+ * This example only works for a flat namespace below the node DN
+ */
+int alpha_distribution(Slapi_PBlock *pb, Slapi_DN * target_dn,
+ char **mtn_be_names, int be_count, Slapi_DN * node_dn)
+{
+ unsigned long op_type;
+ Slapi_Operation *op;
+ char *rdn_type;
+ char *rdn_value;
+ Slapi_RDN *rdn = NULL;
+ char c;
+
+ /* first check the operation type
+ * searches at node level or above it should go in all backends
+ * searches below node level should go in only one backend
+ */
+ slapi_pblock_get(pb, SLAPI_OPERATION, &op);
+ op_type = slapi_op_get_type(op);
+ if ((op_type == SLAPI_OPERATION_SEARCH) &&
+ slapi_sdn_issuffix(node_dn, target_dn))
+ return SLAPI_BE_ALL_BACKENDS;
+
+ /* now choose the backend
+ * anything starting with a char different from a-z or A-Z will
+ * go in backend 0
+ */
+
+ /* get the first char of first value of rdn */
+ rdn = slapi_rdn_new();
+ slapi_sdn_get_rdn(target_dn, rdn);
+ slapi_rdn_get_first(rdn, &rdn_type, &rdn_value);
+ c = rdn_value[0];
+
+ if (!(((c >= 'a') && (c <= 'z')) ||
+ ((c >= 'A') && (c <= 'Z')) ))
+ {
+ return 0;
+ }
+
+ slapi_rdn_free(&rdn);
+
+ /* for entries with rdn starting with alphabetic characters
+ * use the formula : (c - 'A') * be_count/26
+ * to calculate the backend number
+ */
+ return (toupper(c) - 'A') * be_count/26;
+}
+
+/*
+ * Distribute the entries based on a simple hash algorithme
+ */
+int hash_distribution(Slapi_PBlock *pb, Slapi_DN * target_dn,
+ char **mtn_be_names, int be_count, Slapi_DN * node_dn)
+{
+ unsigned long op_type;
+ Slapi_Operation *op;
+ char *rdn_type;
+ char *rdn_value;
+ Slapi_RDN *rdn = NULL;
+ int hash_value;
+
+ /* first check the operation type
+ * searches at node level or above it should go in all backends
+ * searches below node level should go in only one backend
+ */
+ slapi_pblock_get(pb, SLAPI_OPERATION, &op);
+ op_type = slapi_op_get_type(op);
+ if ((op_type == SLAPI_OPERATION_SEARCH) &&
+ slapi_sdn_issuffix(node_dn, target_dn))
+ return SLAPI_BE_ALL_BACKENDS;
+
+ /* now choose the backend
+ */
+
+ /* get the rdn and hash it to compute the backend number
+ * use a simple hash for this example
+ */
+ rdn = slapi_rdn_new();
+ slapi_sdn_get_rdn(target_dn, rdn);
+ slapi_rdn_get_first(rdn, &rdn_type, &rdn_value);
+
+ /* compute the hash value */
+ hash_value = 0;
+ while (*rdn_value)
+ {
+ hash_value += *rdn_value;
+ rdn_value++;
+ }
+ hash_value = hash_value % be_count;
+
+ slapi_rdn_free(&rdn);
+
+ /* use the hash_value as the returned backend number */
+ return hash_value;
+}
+
+/*
+ * This plugin allows to use a local backend in conjonction with
+ * a chaining backend
+ * The ldbm backend is considered a read-only replica of the data
+ * The chaining backend point to a red-write replica of the data
+ * This distribution logic forward the update request to the chaining
+ * backend, and send the search request to the local dbm database
+ *
+ * The mechanism for updating the local read-only replica is not
+ * taken into account by this plugin
+ *
+ * To be able to use it one must define one ldbm backend and one chaining
+ * backend in the mapping tree node
+ *
+ */
+int chaining_distribution(Slapi_PBlock *pb, Slapi_DN * target_dn,
+ char **mtn_be_names, int be_count, Slapi_DN * node_dn)
+{
+ char * requestor_dn;
+ unsigned long op_type;
+ Slapi_Operation *op;
+ int repl_op = 0;
+ int local_backend = -1;
+ int chaining_backend = -1;
+ int i;
+ char * name;
+
+ /* first, we have to decide which backend is the local backend
+ * and which is the chaining one
+ * For the purpose of this example use the backend name :
+ * the backend with name starting with ldbm is local
+ * the bakend with name starting with chaining is remote
+ */
+ local_backend = -1;
+ chaining_backend = -1;
+ for (i=0; i<be_count; i++)
+ {
+ name = mtn_be_names[i];
+ if ((0 == strncmp(name, "ldbm", 4)) ||
+ (0 == strncmp(name, "user", 4)))
+ local_backend = i;
+ else if (0 == strncmp(name, "chaining", 8))
+ chaining_backend = i;
+ }
+
+ /* Check the operation type
+ * read-only operation will go to the local backend
+ * updates operation will go to the chaining backend
+ */
+ slapi_pblock_get(pb, SLAPI_OPERATION, &op);
+ op_type = slapi_op_get_type(op);
+ if ((op_type == SLAPI_OPERATION_SEARCH) ||
+ (op_type == SLAPI_OPERATION_BIND) ||
+ (op_type == SLAPI_OPERATION_UNBIND) ||
+ (op_type == SLAPI_OPERATION_COMPARE))
+ return local_backend;
+
+ /* if the operation is done by directory manager
+ * use local database even for updates because it is an administrative
+ * operation
+ * remarks : one could also use an update DN in the same way
+ * to let update operation go to the local backend when they are done
+ * by specific administrator user but let all the other user
+ * go to the read-write replica
+ */
+ slapi_pblock_get( pb, SLAPI_REQUESTOR_DN, &requestor_dn );
+ if (slapi_dn_isroot(requestor_dn))
+ return local_backend;
+
+ /* if the operation is a replicated operation
+ * use local database even for updates to avoid infinite loops
+ */
+ slapi_pblock_get (pb, SLAPI_IS_REPLICATED_OPERATION, &repl_op);
+ if (repl_op)
+ return local_backend;
+
+ /* all other case (update while not directory manager) :
+ * use the chaining backend
+ */
+ return chaining_backend;
+}
diff --git a/ldap/servers/plugins/distrib/distrib.dsp b/ldap/servers/plugins/distrib/distrib.dsp
new file mode 100644
index 00000000..ebf11f1e
--- /dev/null
+++ b/ldap/servers/plugins/distrib/distrib.dsp
@@ -0,0 +1,116 @@
+# Microsoft Developer Studio Project File - Name="distrib" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 5.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=distrib - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "distrib.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "distrib.mak" CFG="distrib - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "distrib - Win32 Release" (based on\
+ "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "distrib - Win32 Debug" (based on\
+ "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "distrib - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir ".\Release"
+# PROP BASE Intermediate_Dir ".\Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir ".\Release"
+# PROP Intermediate_Dir ".\Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /c
+# ADD CPP /nologo /MD /W3 /GX /O2 /I "..\..\include" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_WIN32" /YX /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /machine:I386
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib wsock32.lib ..\..\lib\libslapd.lib /nologo /subsystem:windows /dll /machine:I386
+
+!ELSEIF "$(CFG)" == "distrib - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir ".\Debug"
+# PROP BASE Intermediate_Dir ".\Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir ".\Debug"
+# PROP Intermediate_Dir ".\Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /c
+# ADD CPP /nologo /MD /W3 /Gm /GX /Zi /Od /I "..\..\include" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_WIN32" /YX /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /d "_DEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /debug /machine:I386
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib wsock32.lib ..\..\lib\libslapd.lib /nologo /subsystem:windows /dll /debug /machine:I386
+
+!ENDIF
+
+# Begin Target
+
+# Name "distrib - Win32 Release"
+# Name "distrib - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90"
+# Begin Source File
+
+SOURCE=.\dllmain.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\distrib.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\libdistrib.def
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd"
+# End Group
+# Begin Group "Resource Files"
+
+# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe"
+# End Group
+# End Target
+# End Project
diff --git a/ldap/servers/plugins/distrib/dllmain.c b/ldap/servers/plugins/distrib/dllmain.c
new file mode 100644
index 00000000..bce5eed7
--- /dev/null
+++ b/ldap/servers/plugins/distrib/dllmain.c
@@ -0,0 +1,101 @@
+/**
+ * PROPRIETARY/CONFIDENTIAL. Use of this product is subject to
+ * license terms. Copyright 2001 Sun Microsystems, Inc.
+ * Some preexisting portions Copyright 2001 Netscape Communications Corp.
+ * All rights reserved.
+ */
+/*
+ * Copyright (C) 2000 Sun Microsystems Inc.
+ *
+ * Use of this Source Code is subject to the terms of the applicable license
+ * agreement from Sun Microsystems Inc.
+ *
+ * The copyright notice(s) in this Source Code does not indicate actual or
+ * intended publication of this Source Code.
+ */
+
+ /*
+ * Microsoft Windows specifics
+ */
+#include "ldap.h"
+
+
+#ifdef _WIN32
+/* Lifted from Q125688
+ * How to Port a 16-bit DLL to a Win32 DLL
+ * on the MSVC 4.0 CD
+ */
+BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved)
+{
+
+ switch (fdwReason)
+ {
+ case DLL_PROCESS_ATTACH:
+ /* Code from LibMain inserted here. Return TRUE to keep the
+ DLL loaded or return FALSE to fail loading the DLL.
+
+ You may have to modify the code in your original LibMain to
+ account for the fact that it may be called more than once.
+ You will get one DLL_PROCESS_ATTACH for each process that
+ loads the DLL. This is different from LibMain which gets
+ called only once when the DLL is loaded. The only time this
+ is critical is when you are using shared data sections.
+ If you are using shared data sections for statically
+ allocated data, you will need to be careful to initialize it
+ only once. Check your code carefully.
+
+ Certain one-time initializations may now need to be done for
+ each process that attaches. You may also not need code from
+ your original LibMain because the operating system may now
+ be doing it for you.
+ */
+ /*
+ * 16 bit code calls UnlockData()
+ * which is mapped to UnlockSegment in windows.h
+ * in 32 bit world UnlockData is not defined anywhere
+ * UnlockSegment is mapped to GlobalUnfix in winbase.h
+ * and the docs for both UnlockSegment and GlobalUnfix say
+ * ".. function is oboslete. Segments have no meaning
+ * in the 32-bit environment". So we do nothing here.
+ */
+
+ break;
+
+ case DLL_THREAD_ATTACH:
+ /* Called each time a thread is created in a process that has
+ already loaded (attached to) this DLL. Does not get called
+ for each thread that exists in the process before it loaded
+ the DLL.
+
+ Do thread-specific initialization here.
+ */
+ break;
+
+ case DLL_THREAD_DETACH:
+ /* Same as above, but called when a thread in the process
+ exits.
+
+ Do thread-specific cleanup here.
+ */
+ break;
+
+ case DLL_PROCESS_DETACH:
+ /* Code from _WEP inserted here. This code may (like the
+ LibMain) not be necessary. Check to make certain that the
+ operating system is not doing it for you.
+ */
+
+ break;
+ }
+ /* The return value is only used for DLL_PROCESS_ATTACH; all other
+ conditions are ignored. */
+ return TRUE; /* successful DLL_PROCESS_ATTACH */
+}
+#else
+int CALLBACK
+LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine )
+{
+ /*UnlockData( 0 );*/
+ return( 1 );
+}
+#endif
diff --git a/ldap/servers/plugins/distrib/libdistrib.def b/ldap/servers/plugins/distrib/libdistrib.def
new file mode 100644
index 00000000..baef2027
--- /dev/null
+++ b/ldap/servers/plugins/distrib/libdistrib.def
@@ -0,0 +1,10 @@
+;-------------------------------------------------------------------------
+; PROPRIETARY/CONFIDENTIAL. Use of this product is subject to
+; license terms. Copyright 2001 Sun Microsystems, Inc.
+; Some preexisting portions Copyright 2001 Netscape Communications Corp.
+; All rights reserved.
+;-------------------------------------------------------------------------
+DESCRIPTION 'Netscape Directory Server 7 distribution logic example'
+EXPORTS
+ alpha_distribution @1
+ hash_distribution @2
diff --git a/ldap/servers/plugins/http/Makefile b/ldap/servers/plugins/http/Makefile
new file mode 100644
index 00000000..d23c26a5
--- /dev/null
+++ b/ldap/servers/plugins/http/Makefile
@@ -0,0 +1,80 @@
+#
+# PROPRIETARY/CONFIDENTIAL. Use of this product is subject to
+# license terms. Copyright 2001 Sun Microsystems, Inc.
+# Some preexisting portions Copyright 2001 Netscape Communications Corp.
+# All rights reserved.
+#
+LDAP_SRC = ../../..
+MCOM_ROOT = ../../../../..
+
+NOSTDCLEAN=true # don't let nsconfig.mk define target clean
+NOSTDSTRIP=true # don't let nsconfig.mk define target strip
+NSPR20=true # probably should be defined somewhere else (not sure where)
+
+OBJDEST = $(OBJDIR)/lib/libhttpclient
+LIBDIR = $(LIB_RELDIR)
+
+include $(MCOM_ROOT)/ldapserver/nsdefs.mk
+include $(MCOM_ROOT)/ldapserver/nsconfig.mk
+include $(LDAP_SRC)/nsldap.mk
+
+ifeq ($(ARCH), WINNT)
+DEF_FILE:=./http.def
+endif
+
+HTTP_OBJS = http_client.o http_impl.o
+
+OBJS = $(addprefix $(OBJDEST)/, $(HTTP_OBJS))
+
+HTTP_DLL = http-client-plugin
+
+INCLUDES += -I../../slapd -I../../../include
+
+CFLAGS+=$(SLCFLAGS) -DSLAPD_LOGGING
+
+ifeq ($(ARCH), WINNT)
+CFLAGS+=-D_WIN32 -DXP_WIN -DXP_WIN32
+EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(NSPR_DEP) $(SECURITY_DEP)
+EXTRA_LIBS += $(NSPRLINK) $(LIBSLAPD) $(SECURITYLINK)
+HTTP_DLL_OBJ = $(addprefix $(OBJDEST)/, dllmain.o)
+endif
+
+ifeq ($(ARCH), HPUX)
+EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAPSDK_DEP) $(NSPR_DEP) $(SECURITY_DEP)
+EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(SECURITYLINK) $(NSPRLINK) $(ICULINK)
+endif
+
+ifeq ($(ARCH), AIX)
+LD=ld
+EXTRA_LIBS += $(LIBSLAPD)
+endif
+
+HTTP= $(addprefix $(LIBDIR)/, $(HTTP_DLL).$(DLL_SUFFIX))
+
+clientSDK:
+
+all: $(OBJDEST) $(LIBDIR) $(HTTP)
+
+ifeq ($(ARCH), WINNT)
+$(HTTP): $(OBJS) $(HTTP_DLL_OBJ) $(DEF_FILE)
+ $(LINK_DLL) $(HTTP_DLL_OBJ) $(EXTRA_LIBS) /DEF:$(DEF_FILE)
+else
+$(HTTP): $(OBJS) $(HTTP_DLL_OBJ)
+ $(LINK_DLL) $(HTTP_DLL_OBJ) $(EXTRA_LIBS)
+endif
+
+
+veryclean: clean
+
+clean:
+ $(RM) $(OBJS)
+ifeq ($(ARCH), WINNT)
+ $(RM) $(HTTP_DLL_OBJ)
+endif
+ $(RM) $(HTTP)
+
+$(OBJDEST):
+ $(MKDIR) $(OBJDEST)
+
+$(LIBDIR):
+ $(MKDIR) $(LIBDIR)
diff --git a/ldap/servers/plugins/http/dllmain.c b/ldap/servers/plugins/http/dllmain.c
new file mode 100644
index 00000000..ef1f637a
--- /dev/null
+++ b/ldap/servers/plugins/http/dllmain.c
@@ -0,0 +1,98 @@
+/**
+ * PROPRIETARY/CONFIDENTIAL. Use of this product is subject to
+ * license terms. Copyright 2001 Sun Microsystems, Inc.
+ * Some preexisting portions Copyright 2001 Netscape Communications Corp.
+ * All rights reserved.
+ */
+/*
+ * Microsoft Windows specifics for BACK-LDBM DLL
+ */
+#include "ldap.h"
+#include "lber.h"
+
+#ifdef _WIN32
+
+/* Lifted from Q125688
+ * How to Port a 16-bit DLL to a Win32 DLL
+ * on the MSVC 4.0 CD
+ */
+
+BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved)
+{
+ WSADATA wsadata;
+
+ switch (fdwReason)
+ {
+ case DLL_PROCESS_ATTACH:
+ /* Code from LibMain inserted here. Return TRUE to keep the
+ DLL loaded or return FALSE to fail loading the DLL.
+
+ You may have to modify the code in your original LibMain to
+ account for the fact that it may be called more than once.
+ You will get one DLL_PROCESS_ATTACH for each process that
+ loads the DLL. This is different from LibMain which gets
+ called only once when the DLL is loaded. The only time this
+ is critical is when you are using shared data sections.
+ If you are using shared data sections for statically
+ allocated data, you will need to be careful to initialize it
+ only once. Check your code carefully.
+
+ Certain one-time initializations may now need to be done for
+ each process that attaches. You may also not need code from
+ your original LibMain because the operating system may now
+ be doing it for you.
+ */
+ /*
+ * 16 bit code calls UnlockData()
+ * which is mapped to UnlockSegment in windows.h
+ * in 32 bit world UnlockData is not defined anywhere
+ * UnlockSegment is mapped to GlobalUnfix in winbase.h
+ * and the docs for both UnlockSegment and GlobalUnfix say
+ * ".. function is oboslete. Segments have no meaning
+ * in the 32-bit environment". So we do nothing here.
+ */
+
+ if( errno = WSAStartup(0x0101, &wsadata ) != 0 )
+ return FALSE;
+
+ break;
+
+ case DLL_THREAD_ATTACH:
+ /* Called each time a thread is created in a process that has
+ already loaded (attached to) this DLL. Does not get called
+ for each thread that exists in the process before it loaded
+ the DLL.
+
+ Do thread-specific initialization here.
+ */
+ break;
+
+ case DLL_THREAD_DETACH:
+ /* Same as above, but called when a thread in the process
+ exits.
+
+ Do thread-specific cleanup here.
+ */
+ break;
+
+ case DLL_PROCESS_DETACH:
+ /* Code from _WEP inserted here. This code may (like the
+ LibMain) not be necessary. Check to make certain that the
+ operating system is not doing it for you.
+ */
+ WSACleanup();
+
+ break;
+ }
+ /* The return value is only used for DLL_PROCESS_ATTACH; all other
+ conditions are ignored. */
+ return TRUE; /* successful DLL_PROCESS_ATTACH */
+}
+#else
+int CALLBACK
+LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine )
+{
+ /*UnlockData( 0 );*/
+ return( 1 );
+}
+#endif
diff --git a/ldap/servers/plugins/http/http.def b/ldap/servers/plugins/http/http.def
new file mode 100644
index 00000000..ac2f3e05
--- /dev/null
+++ b/ldap/servers/plugins/http/http.def
@@ -0,0 +1,13 @@
+;-------------------------------------------------------------------------
+; PROPRIETARY/CONFIDENTIAL. Use of this product is subject to
+; license terms. Copyright 2001 Sun Microsystems, Inc.
+; Some preexisting portions Copyright 2001 Netscape Communications Corp.
+; All rights reserved.
+;-------------------------------------------------------------------------
+DESCRIPTION 'Netscape Directory Server Http Client'
+EXPORTS
+ http_client_init @2
+ plugin_init_debug_level @3
+ http_client_version @4
+
+
diff --git a/ldap/servers/plugins/http/http_client.c b/ldap/servers/plugins/http/http_client.c
new file mode 100644
index 00000000..2fbbdd52
--- /dev/null
+++ b/ldap/servers/plugins/http/http_client.c
@@ -0,0 +1,290 @@
+/**
+ * PROPRIETARY/CONFIDENTIAL. Use of this product is subject to
+ * license terms. Copyright 2001 Sun Microsystems, Inc.
+ * Some preexisting portions Copyright 2001 Netscape Communications Corp.
+ * All rights reserved.
+ */
+
+/**
+ * Simple Http Client API broker plugin
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "portable.h"
+#include "nspr.h"
+
+#include "slapi-plugin.h"
+#include "slapi-private.h"
+#include "dirlite_strings.h"
+#include "dirver.h"
+
+#include "http_client.h"
+#include "http_impl.h"
+
+/* get file mode flags for unix */
+#ifndef _WIN32
+#include <sys/stat.h>
+#endif
+
+/*** from proto-slap.h ***/
+
+int slapd_log_error_proc( char *subsystem, char *fmt, ... );
+
+/*** from ldaplog.h ***/
+
+/* edited ldaplog.h for LDAPDebug()*/
+#ifndef _LDAPLOG_H
+#define _LDAPLOG_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define LDAP_DEBUG_TRACE 0x00001 /* 1 */
+#define LDAP_DEBUG_ANY 0x04000 /* 16384 */
+#define LDAP_DEBUG_PLUGIN 0x10000 /* 65536 */
+
+/* debugging stuff */
+# ifdef _WIN32
+ extern int *module_ldap_debug;
+# define LDAPDebug( level, fmt, arg1, arg2, arg3 ) \
+ { \
+ if ( *module_ldap_debug & level ) { \
+ slapd_log_error_proc( NULL, fmt, arg1, arg2, arg3 ); \
+ } \
+ }
+# else /* _WIN32 */
+ extern int slapd_ldap_debug;
+# define LDAPDebug( level, fmt, arg1, arg2, arg3 ) \
+ { \
+ if ( slapd_ldap_debug & level ) { \
+ slapd_log_error_proc( NULL, fmt, arg1, arg2, arg3 ); \
+ } \
+ }
+# endif /* Win32 */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _LDAP_H */
+
+#define HTTP_PLUGIN_SUBSYSTEM "http-client-plugin" /* used for logging */
+#define HTTP_PLUGIN_VERSION 0x00050050
+
+#define HTTP_SUCCESS 0
+#define HTTP_FAILURE -1
+
+/**
+ * Implementation functions
+ */
+static void *api[7];
+
+/**
+ * Plugin identifiers
+ */
+static Slapi_PluginDesc pdesc = { "http-client",
+ PLUGIN_MAGIC_VENDOR_STR,
+ PRODUCTTEXT,
+ "HTTP Client plugin" };
+
+static Slapi_ComponentId *plugin_id = NULL;
+
+/**
+ **
+ ** Http plug-in management functions
+ **
+ **/
+int http_client_init(Slapi_PBlock *pb);
+static int http_client_start(Slapi_PBlock *pb);
+static int http_client_close(Slapi_PBlock *pb);
+
+/**
+ * our functions
+ */
+static void _http_init(Slapi_ComponentId *plugin_id);
+static int _http_get_text(char *url, char **data, int *bytesRead);
+static int _http_get_binary(char *url, char **data, int *bytesRead);
+static int _http_get_redirected_uri(char *url, char **data, int *bytesRead);
+static int _http_post(char *url, httpheader **httpheaderArray, char *body, char **data, int *bytesRead);
+static void _http_shutdown( void );
+
+#ifdef _WIN32
+int *module_ldap_debug = 0;
+
+void plugin_init_debug_level(int *level_ptr)
+{
+ module_ldap_debug = level_ptr;
+}
+#endif
+
+/**
+ *
+ * Get the presence plug-in version
+ *
+ */
+int http_client_version()
+{
+ return HTTP_PLUGIN_VERSION;
+}
+
+int http_client_init(Slapi_PBlock *pb)
+{
+ int status = HTTP_SUCCESS;
+ PRUint32 nssFlags = 0;
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "--> http_client_init -- BEGIN\n",0,0,0);
+
+ if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION,
+ SLAPI_PLUGIN_VERSION_01 ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN,
+ (void *) http_client_start ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN,
+ (void *) http_client_close ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION,
+ (void *)&pdesc ) != 0 )
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM,
+ "http_client_init: failed to register plugin\n" );
+ status = HTTP_FAILURE;
+ }
+
+ /* Retrieve and save the plugin identity to later pass to
+ internal operations */
+ if (slapi_pblock_get(pb, SLAPI_PLUGIN_IDENTITY, &plugin_id) != 0) {
+ slapi_log_error(SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM,
+ "http_client_init: Failed to retrieve SLAPI_PLUGIN_IDENTITY\n");
+ return HTTP_FAILURE;
+ }
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- http_client_init -- END\n",0,0,0);
+ return status;
+}
+
+static int http_client_start(Slapi_PBlock *pb)
+{
+ int status = HTTP_SUCCESS;
+ /**
+ * do some init work here
+ */
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "--> http_client_start -- BEGIN\n",0,0,0);
+
+ api[0] = 0; /* reserved for api broker use, must be zero */
+ api[1] = (void *)_http_init;
+ api[2] = (void *)_http_get_text;
+ api[3] = (void *)_http_get_binary;
+ api[4] = (void *)_http_get_redirected_uri;
+ api[5] = (void *)_http_shutdown;
+ api[6] = (void *)_http_post;
+
+ if( slapi_apib_register(HTTP_v1_0_GUID, api) ) {
+ slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM,
+ "http_client_start: failed to register functions\n" );
+ status = HTTP_FAILURE;
+ }
+
+ _http_init(plugin_id);
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- http_client_start -- END\n",0,0,0);
+ return status;
+}
+
+static int http_client_close(Slapi_PBlock *pb)
+{
+ int status = HTTP_SUCCESS;
+ /**
+ * do cleanup
+ */
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "--> http_client_close -- BEGIN\n",0,0,0);
+
+ slapi_apib_unregister(HTTP_v1_0_GUID);
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- http_client_close -- END\n",0,0,0);
+
+ return status;
+}
+
+/**
+ * perform http initialization here
+ */
+static void _http_init(Slapi_ComponentId *plugin_id)
+{
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "--> _http_init -- BEGIN\n",0,0,0);
+
+ http_impl_init(plugin_id);
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- _http_init -- END\n",0,0,0);
+}
+
+/**
+ * This method gets the data in a text format based on the
+ * URL send.
+ */
+static int _http_get_text(char *url, char **data, int *bytesRead)
+{
+ int status = HTTP_SUCCESS;
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "--> _http_get_text -- BEGIN\n",0,0,0);
+
+ status = http_impl_get_text(url, data, bytesRead);
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- _http_get_text -- END\n",0,0,0);
+ return status;
+}
+
+/**
+ * This method gets the data in a binary format based on the
+ * URL send.
+ */
+static int _http_get_binary(char *url, char **data, int *bytesRead)
+{
+ int status = HTTP_SUCCESS;
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "--> _http_get_binary -- BEGIN\n",0,0,0);
+
+ status = http_impl_get_binary(url, data, bytesRead);
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- _http_get_binary -- END\n",0,0,0);
+ return status;
+}
+
+/**
+ * This method intercepts the redirected URI and returns the location
+ * information.
+ */
+static int _http_get_redirected_uri(char *url, char **data, int *bytesRead)
+{
+ int status = HTTP_SUCCESS;
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "--> _http_get_redirected_uri -- BEGIN\n",0,0,0);
+
+ status = http_impl_get_redirected_uri(url, data, bytesRead);
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- _http_get_redirected_uri -- END\n",0,0,0);
+ return status;
+}
+
+/**
+ * This method posts the data based on the URL send.
+ */
+static int _http_post(char *url, httpheader ** httpheaderArray, char *body, char **data, int *bytesRead)
+{
+ int status = HTTP_SUCCESS;
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "--> _http_post -- BEGIN\n",0,0,0);
+
+ status = http_impl_post(url, httpheaderArray, body, data, bytesRead);
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- _http_post -- END\n",0,0,0);
+ return status;
+}
+
+/**
+ * perform http shutdown here
+ */
+static void _http_shutdown( void )
+{
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "--> _http_shutdown -- BEGIN\n",0,0,0);
+
+ http_impl_shutdown();
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- _http_shutdown -- END\n",0,0,0);
+}
+
diff --git a/ldap/servers/plugins/http/http_client.h b/ldap/servers/plugins/http/http_client.h
new file mode 100644
index 00000000..d849e18d
--- /dev/null
+++ b/ldap/servers/plugins/http/http_client.h
@@ -0,0 +1,64 @@
+/**
+ * PROPRIETARY/CONFIDENTIAL. Use of this product is subject to
+ * license terms. Copyright 2001 Sun Microsystems, Inc.
+ * Some preexisting portions Copyright 2001 Netscape Communications Corp.
+ * All rights reserved.
+ */
+
+#ifndef _HTTP_CLIENT_H_
+#define _HTTP_CLIENT_H_
+
+/* Error codes */
+#define HTTP_CLIENT_ERROR_BAD_URL -1
+#define HTTP_CLIENT_ERROR_NET_ADDR -2
+#define HTTP_CLIENT_ERROR_SOCKET_CREATE -3
+#define HTTP_CLIENT_ERROR_CONNECT_FAILED -4
+#define HTTP_CLIENT_ERROR_SEND_REQ -5
+#define HTTP_CLIENT_ERROR_BAD_RESPONSE -6
+#define HTTP_CLIENT_ERROR_SSLSOCKET_CREATE -7
+ #define HTTP_CLIENT_ERROR_NSS_INITIALIZE -8
+
+/*Structure to store HTTP Headers */
+typedef struct {
+ char *name;
+ char *value;
+} httpheader;
+
+
+/* mechanics */
+
+
+typedef void (*api_http_init)(Slapi_ComponentId *plugin_id);
+typedef int (*api_http_get_text)(char *url, char **data, int *bytesRead);
+typedef int (*api_http_get_binary)(char *url, char **data, int *bytesRead);
+typedef int (*api_http_get_redirected_uri)(char *url, char **data, int *bytesRead);
+typedef void (*api_http_shutdown)();
+typedef int (*api_http_post)(char *url, httpheader **httpheaderArray, char *body, char **data, int *bytesRead);
+
+/* API ID for http_apib_get_interface */
+
+#define HTTP_v1_0_GUID "811c5ea2-fef4-4f1c-9ab4-fcf746cd6efc"
+
+/* API */
+
+/* the api broker reserves api[0] for its use */
+
+#define http_init(api) \
+ ((api_http_init*)(api))[1](Slapi_ComponentId *plugin_id)
+
+#define http_get_text(api, url, data, bytesRead) \
+ ((api_http_get_text*)(api))[2]( url, data, bytesRead)
+
+#define http_get_binary(api, url, data, bytesRead) \
+ ((api_http_get_binary*)(api))[3](url, data, bytesRead)
+
+#define http_get_redirected_uri(api, url, data, bytesRead) \
+ ((api_http_get_redirected_uri*)(api))[4](url, data, bytesRead)
+
+#define http_shutdown(api) \
+ ((api_http_shutdown*)(api))[5]()
+
+#define http_post(api, url, httpheaderArray, body, data, bytesRead) \
+ ((api_http_post*)(api))[6](url, httpheaderArray, body, data, bytesRead)
+
+#endif /*_HTTP_CLIENT_H_*/
diff --git a/ldap/servers/plugins/http/http_impl.c b/ldap/servers/plugins/http/http_impl.c
new file mode 100644
index 00000000..bad8315c
--- /dev/null
+++ b/ldap/servers/plugins/http/http_impl.c
@@ -0,0 +1,1479 @@
+/**
+ * PROPRIETARY/CONFIDENTIAL. Use of this product is subject to
+ * license terms. Copyright 2001 Sun Microsystems, Inc.
+ * Some preexisting portions Copyright 2001 Netscape Communications Corp.
+ * All rights reserved.
+ */
+/**
+ * Implementation of a Simple HTTP Client
+ */
+#include <stdio.h>
+#include <string.h>
+
+#include "nspr.h"
+#include "nss.h"
+#include "pk11func.h"
+#include "ssl.h"
+#include "prprf.h"
+#include "plstr.h"
+#include "slapi-plugin.h"
+#include "http_client.h"
+#include "secerr.h"
+#include "sslerr.h"
+#include "slapi-private.h"
+#include "slapi-plugin-compat4.h"
+/* get file mode flags for unix */
+#ifndef _WIN32
+#include <sys/stat.h>
+#endif
+
+/*** from proto-slap.h ***/
+
+int slapd_log_error_proc( char *subsystem, char *fmt, ... );
+char *config_get_instancedir();
+
+/*** from ldaplog.h ***/
+
+/* edited ldaplog.h for LDAPDebug()*/
+#ifndef _LDAPLOG_H
+#define _LDAPLOG_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef BUILD_STANDALONE
+#define slapi_log_error(a,b,c,d) printf((c),(d))
+#define stricmp strcasecmp
+#endif
+
+#define LDAP_DEBUG_TRACE 0x00001 /* 1 */
+#define LDAP_DEBUG_ANY 0x04000 /* 16384 */
+#define LDAP_DEBUG_PLUGIN 0x10000 /* 65536 */
+
+/* debugging stuff */
+# ifdef _WIN32
+ extern int *module_ldap_debug;
+# define LDAPDebug( level, fmt, arg1, arg2, arg3 ) \
+ { \
+ if ( *module_ldap_debug & level ) { \
+ slapd_log_error_proc( NULL, fmt, arg1, arg2, arg3 ); \
+ } \
+ }
+# else /* _WIN32 */
+ extern int slapd_ldap_debug;
+# define LDAPDebug( level, fmt, arg1, arg2, arg3 ) \
+ { \
+ if ( slapd_ldap_debug & level ) { \
+ slapd_log_error_proc( NULL, fmt, arg1, arg2, arg3 ); \
+ } \
+ }
+# endif /* Win32 */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _LDAP_H */
+
+#define HTTP_PLUGIN_SUBSYSTEM "http-client-plugin" /* used for logging */
+
+#define HTTP_IMPL_SUCCESS 0
+#define HTTP_IMPL_FAILURE -1
+
+#define HTTP_REQ_TYPE_GET 1
+#define HTTP_REQ_TYPE_REDIRECT 2
+#define HTTP_REQ_TYPE_POST 3
+
+#define HTTP_GET "GET"
+#define HTTP_POST "POST"
+#define HTTP_PROTOCOL "HTTP/1.0"
+#define HTTP_CONTENT_LENGTH "Content-length:"
+#define HTTP_CONTENT_TYPE_URL_ENCODED "Content-type: application/x-www-form-urlencoded"
+#define HTTP_GET_STD_LEN 18
+#define HTTP_POST_STD_LEN 85
+#define HTTP_DEFAULT_BUFFER_SIZE 4096
+#define HTTP_RESPONSE_REDIRECT (retcode == 302 || retcode == 301)
+
+/**
+ * Error strings used for logging error messages
+ */
+#define HTTP_ERROR_BAD_URL " Badly formatted URL"
+#define HTTP_ERROR_NET_ADDR " NetAddr initialization failed"
+#define HTTP_ERROR_SOCKET_CREATE " Creation of socket failed"
+#define HTTP_ERROR_SSLSOCKET_CREATE " Creation of SSL socket failed"
+#define HTTP_ERROR_CONNECT_FAILED " Couldn't connect to remote host"
+#define HTTP_ERROR_SEND_REQ " Send request failed"
+#define HTTP_ERROR_BAD_RESPONSE " Invalid response from remote host"
+
+#define HTTP_PLUGIN_DN "cn=HTTP Client,cn=plugins,cn=config"
+#define CONFIG_DN "cn=config"
+#define ATTR_CONNECTION_TIME_OUT "nsHTTPConnectionTimeOut"
+#define ATTR_READ_TIME_OUT "nsHTTPReadTimeOut"
+#define ATTR_RETRY_COUNT "nsHTTPRetryCount"
+#define ATTR_DS_SECURITY "nsslapd-security"
+#define ATTR_INSTANCE_PATH "nsslapd-errorlog"
+
+/*static Slapi_ComponentId *plugin_id = NULL;*/
+
+typedef struct {
+ int retryCount;
+ int connectionTimeOut;
+ int readTimeOut;
+ int nssInitialized;
+ char *DS_sslOn;
+} httpPluginConfig;
+
+httpPluginConfig *httpConfig;
+
+/**
+ * Public functions
+ */
+int http_impl_init(Slapi_ComponentId *plugin_id);
+int http_impl_get_text(char *url, char **data, int *bytesRead);
+int http_impl_get_binary(char *url, char **data, int *bytesRead);
+int http_impl_get_redirected_uri(char *url, char **data, int *bytesRead);
+int http_impl_post(char *url, httpheader **httpheaderArray, char *body, char **data, int *bytesRead);
+void http_impl_shutdown();
+
+/**
+ * Http handling functions
+ */
+static int doRequest(const char *url, httpheader **httpheaderArray, char *body, char **buf, int *bytesRead, int reqType);
+static int doRequestRetry(const char *url, httpheader **httpheaderArray, char *body, char **buf, int *bytesRead, int reqType);
+static void setTCPNoDelay(PRFileDesc* fd);
+static PRStatus sendGetReq(PRFileDesc *fd, const char *path);
+static PRStatus sendPostReq(PRFileDesc *fd, const char *path, httpheader **httpheaderArray, char *body);
+static PRStatus processResponse(PRFileDesc *fd, char **resBUF, int *bytesRead, int reqType);
+static PRStatus getChar(PRFileDesc *fd, char *buf);
+static PRInt32 http_read(PRFileDesc *fd, char *buf, int size);
+static PRStatus getBody(PRFileDesc *fd, char **buf, int *actualBytesRead);
+static PRBool isWhiteSpace(char ch);
+static PRStatus sendFullData( PRFileDesc *fd, char *buf, int timeOut);
+
+/**
+ * Helper functions to parse URL
+ */
+static PRStatus parseURI(const char *url, char **host, PRInt32 *port, char **path, int *sslOn);
+static void toLowerCase(char* str);
+static PRStatus parseAtPort(const char* url, PRInt32 *port, char **path);
+static PRStatus parseAtPath(const char *url, char **path);
+static PRInt32 getPort(const char* src);
+static PRBool isAsciiSpace(char aChar);
+static PRBool isAsciiDigit(char aChar);
+static char * isHttpReq(const char *url, int *sslOn);
+
+/*To get config from entry*/
+static int readConfigLDAPurl(Slapi_ComponentId *plugin_id, char *plugindn);
+static int parseHTTPConfigEntry(Slapi_Entry *e);
+static int parseConfigEntry(Slapi_Entry *e);
+
+static int nssReinitializationRequired();
+
+/*SSL functions */
+PRFileDesc* setupSSLSocket(PRFileDesc* fd);
+
+/*SSL callback functions */
+SECStatus badCertHandler(void *arg, PRFileDesc *socket);
+SECStatus authCertificate(void *arg, PRFileDesc *socket, PRBool checksig, PRBool isServer);
+SECStatus getClientAuthData(void *arg, PRFileDesc *socket,struct CERTDistNamesStr *caNames, struct CERTCertificateStr **pRetCert, struct SECKEYPrivateKeyStr **pRetKey);
+SECStatus handshakeCallback(PRFileDesc *socket, void *arg);
+
+static int doRequestRetry(const char *url, httpheader **httpheaderArray, char *body, char **buf, int *bytesRead, int reqType)
+{
+ int status = HTTP_IMPL_SUCCESS;
+ int retrycnt = 0;
+ int i = 1;
+
+ retrycnt = httpConfig->retryCount;
+
+ if (retrycnt == 0) {
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "doRequestRetry: Retry Count cannot be read. Setting to default value of 3 \n", 0,0,0);
+ retrycnt = 3;
+ }
+ status = doRequest(url, httpheaderArray, body, buf, bytesRead, reqType);
+ if (status != HTTP_IMPL_SUCCESS) {
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "doRequestRetry: Failed to perform http request \n", 0,0,0);
+ while (retrycnt > 0) {
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "doRequestRetry: Retrying http request %d.\n", i,0,0);
+ status = doRequest(url, httpheaderArray, body, buf, bytesRead, reqType);
+ if (status == HTTP_IMPL_SUCCESS) {
+ break;
+ }
+ retrycnt--;
+ i++;
+ }
+ if (status != HTTP_IMPL_SUCCESS) {
+ LDAPDebug( LDAP_DEBUG_ANY, "doRequestRetry: Failed to perform http request after %d attempts.\n", i,0,0);
+ LDAPDebug( LDAP_DEBUG_ANY, "doRequestRetry: Verify plugin URI configuration and contact Directory Administrator.\n",0,0,0);
+ }
+
+ }
+ return status;
+}
+
+static int doRequest(const char *url, httpheader **httpheaderArray, char *body, char **buf, int *bytesRead, int reqType)
+{
+ PRStatus status = PR_SUCCESS;
+
+ char *host = NULL;
+ char *path = NULL;
+ char *val = NULL;
+ char *defaultprefix = NULL;
+ PRFileDesc *fd = NULL;
+ PRNetAddr addr;
+ PRInt32 port;
+ PRInt32 errcode = 0;
+ PRInt32 http_connection_time_out = 0;
+ PRInt32 sslOn;
+ PRInt32 nssStatus;
+ PRUint32 nssFlags = 0;
+ char certDir[1024];
+ char certPref[1024];
+ char keyPref[1024];
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "--> doRequest -- BEGIN\n",0,0,0);
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> url=[%s] \n",url,0,0);
+
+ /* Parse the URL and initialize the host, port, path */
+ if (parseURI(url, &host, &port, &path, &sslOn) == PR_FAILURE) {
+ slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM,
+ "doRequest: %s \n", HTTP_ERROR_BAD_URL);
+ status = PR_FAILURE;
+ goto bail;
+ }
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> host=[%s] port[%d] path[%s] \n",host,port,path);
+
+ /* Initialize the Net Addr */
+ if (PR_StringToNetAddr(host, &addr) == PR_FAILURE) {
+ char buf[PR_NETDB_BUF_SIZE];
+ PRHostEnt ent;
+
+ status = PR_GetIPNodeByName(host, PR_AF_INET, PR_AI_DEFAULT, buf, sizeof(buf), &ent);
+ if (status == PR_SUCCESS) {
+ PR_EnumerateHostEnt(0, &ent, (PRUint16)port, &addr);
+ } else {
+ slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM,
+ "doRequest: %s\n", HTTP_ERROR_NET_ADDR);
+ status = HTTP_CLIENT_ERROR_NET_ADDR;
+ goto bail;
+ }
+ } else {
+ addr.inet.port = (PRUint16)port;
+ }
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Successfully created NetAddr \n",0,0,0);
+
+ /* open a TCP connection to the server */
+ fd = PR_NewTCPSocket();
+ if (!fd) {
+ slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM,
+ "doRequest: %s\n", HTTP_ERROR_SOCKET_CREATE);
+ status = HTTP_CLIENT_ERROR_SOCKET_CREATE;
+ goto bail;
+ }
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Successfully created New TCP Socket \n",0,0,0);
+
+ /* immediately send the response */
+ setTCPNoDelay(fd);
+
+ if (sslOn) {
+
+ /* Have to reinitialize NSS is the DS security is set to off.
+ This is because the HTTPS required the cert dbs to be created.
+ The default prefixes are used as per DS norm */
+
+ if (PL_strcasecmp(httpConfig->DS_sslOn, "off") == 0) {
+ if (!httpConfig->nssInitialized) {
+ if (nssReinitializationRequired())
+ {
+ NSS_Shutdown();
+ nssFlags &= (~NSS_INIT_READONLY);
+ val = config_get_instancedir();
+ strcpy(certDir, val);
+ defaultprefix = strrchr(certDir, '/');
+ if (!defaultprefix)
+ defaultprefix = strrchr(certDir, '\\');
+ if (!defaultprefix) /* still could not find it . . . */
+ goto bail; /* . . . can't do anything */
+ defaultprefix++;
+ sprintf(certPref, "%s-",defaultprefix);
+ strcpy(keyPref, certPref);
+ *defaultprefix= '\0';
+ sprintf(certDir, "%salias", certDir);
+ nssStatus = NSS_Initialize(certDir, certPref, keyPref, "secmod.db", nssFlags);
+ slapi_ch_free((void **)&val);
+
+ if (nssStatus != 0) {
+ slapi_log_error(SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM,
+ "doRequest: Unable to initialize NSS Cert/Key Database\n");
+ status = HTTP_CLIENT_ERROR_NSS_INITIALIZE;
+ goto bail;
+ }
+ }
+ httpConfig->nssInitialized = 1;
+ }
+ }
+
+ NSS_SetDomesticPolicy();
+
+ fd = setupSSLSocket(fd);
+ if (fd == NULL) {
+ slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM,
+ "doRequest: %s\n", HTTP_ERROR_SSLSOCKET_CREATE);
+ status = HTTP_CLIENT_ERROR_SSLSOCKET_CREATE;
+ goto bail;
+ }
+
+ if (SSL_SetURL(fd, host) != 0) {
+ errcode = PR_GetError();
+ slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM,
+ "doRequest: SSL_SetURL -> NSPR Error code (%d) \n", errcode);
+ status = HTTP_CLIENT_ERROR_SSLSOCKET_CREATE;
+ goto bail;
+ }
+
+ }
+
+ http_connection_time_out = httpConfig->connectionTimeOut;
+ /* connect to the host */
+ if (PR_Connect(fd, &addr, PR_MillisecondsToInterval(http_connection_time_out)) == PR_FAILURE) {
+ errcode = PR_GetError();
+ slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM,
+ "doRequest: %s (%s:%d) -> NSPR Error code (%d)\n",
+ HTTP_ERROR_CONNECT_FAILED, host, addr.inet.port, errcode);
+ status = HTTP_CLIENT_ERROR_CONNECT_FAILED;
+ goto bail;
+ }
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Successfully connected to host [%s] \n",host,0,0);
+
+ /* send the request to the server */
+ if (reqType == HTTP_REQ_TYPE_POST) {
+ if (sendPostReq(fd, path, httpheaderArray, body) == PR_FAILURE) {
+ slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM,
+ "doRequest-sendPostReq: %s (%s)\n", HTTP_ERROR_SEND_REQ, path);
+ status = HTTP_CLIENT_ERROR_SEND_REQ;
+ goto bail;
+ }
+ }
+ else {
+ if (sendGetReq(fd, path) == PR_FAILURE) {
+ slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM,
+ "doRequest-sendGetReq: %s (%s)\n", HTTP_ERROR_SEND_REQ, path);
+ status = HTTP_CLIENT_ERROR_SEND_REQ;
+ goto bail;
+ }
+ }
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Successfully sent the request [%s] \n",path,0,0);
+
+ /* read the response */
+ if (processResponse(fd, buf, bytesRead, reqType) == PR_FAILURE) {
+ slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM,
+ "doRequest: %s (%s)\n", HTTP_ERROR_BAD_RESPONSE, url);
+ status = HTTP_CLIENT_ERROR_BAD_RESPONSE;
+ goto bail;
+ }
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Successfully read the response\n",0,0,0);
+bail:
+ if (host) {
+ PR_Free(host);
+ }
+ if (path) {
+ PR_Free(path);
+ }
+ if (fd) {
+ PR_Close(fd);
+ fd = NULL;
+ }
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- doRequest -- END\n",0,0,0);
+ return status;
+}
+
+static PRStatus processResponse(PRFileDesc *fd, char **resBUF, int *bytesRead, int reqType)
+{
+ PRStatus status = PR_SUCCESS;
+ char *location = NULL;
+ char *protocol = NULL;
+ char *statusNum = NULL;
+ char *statusString = NULL;
+ char *headers = NULL;
+
+ char tmp[HTTP_DEFAULT_BUFFER_SIZE];
+ int pos=0;
+ char ch;
+ int index;
+ int retcode;
+
+ PRBool doneParsing = PR_FALSE;
+ PRBool isRedirect = PR_FALSE;
+ char name[HTTP_DEFAULT_BUFFER_SIZE];
+ char value[HTTP_DEFAULT_BUFFER_SIZE];
+ PRBool atEOL = PR_FALSE;
+ PRBool inName = PR_TRUE;
+
+ /* PKBxxx: If we are getting a redirect and the response is more the
+ * the HTTP_DEFAULT_BUFFER_SIZE, it will cause the server to crash. A 4k
+ * buffer should be good enough.
+ */
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "--> processResponse -- BEGIN\n",0,0,0);
+
+ headers = (char *)PR_Calloc(1, 4 * HTTP_DEFAULT_BUFFER_SIZE);
+ /* Get protocol string */
+ index = 0;
+ while (1) {
+ status = getChar(fd, headers+pos);
+ if (status == PR_FAILURE) {
+ /* Error : */
+ goto bail;
+ }
+ ch = (char)headers[pos];
+ pos++;
+ if (!isWhiteSpace(ch)) {
+ tmp[index++] = ch;
+ } else {
+ break;
+ }
+ }
+ tmp[index] = '\0';
+ protocol = PL_strdup(tmp);
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> protocol=[%s] \n",protocol,0,0);
+
+ /* Get status num */
+ index = 0;
+ while (1) {
+ status = getChar(fd, headers+pos);
+ if (status == PR_FAILURE) {
+ /* Error : */
+ goto bail;
+ }
+ ch = (char)headers[pos];
+ pos++;
+ if (!isWhiteSpace(ch)) {
+ tmp[index++] = ch;
+ } else {
+ break;
+ }
+ }
+ tmp[index] = '\0';
+ statusNum = PL_strdup(tmp);
+ retcode=atoi(tmp);
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> statusNum=[%s] \n",statusNum,0,0);
+
+ if (HTTP_RESPONSE_REDIRECT && (reqType == HTTP_REQ_TYPE_REDIRECT)) {
+ isRedirect = PR_TRUE;
+ }
+ /* Get status string */
+ if (ch != '\r')
+ {
+ index = 0;
+ while (ch != '\r') {
+ status = getChar(fd, headers+pos);
+ if (status == PR_FAILURE) {
+ /* Error : */
+ goto bail;
+ }
+ ch = (char)headers[pos];
+ pos++;
+ tmp[index++] = ch;
+ }
+ tmp[index] = '\0';
+ statusString = PL_strdup(tmp);
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> statusString [%s] \n",statusString,0,0);
+ }
+
+ /**
+ * Skip CRLF
+ */
+ status = getChar(fd, headers+pos);
+ if (status == PR_FAILURE) {
+ /* Error : */
+ goto bail;
+ }
+ ch = (char)headers[pos];
+ pos++;
+
+ /**
+ * loop over response headers
+ */
+ index = 0;
+ while (!doneParsing) {
+ status = getChar(fd, headers+pos);
+ if (status == PR_FAILURE) {
+ /* Error : */
+ goto bail;
+ }
+ ch = (char)headers[pos];
+ pos++;
+ switch(ch)
+ {
+ case ':':
+ if (inName) {
+ name[index] = '\0';
+ index = 0;
+ inName = PR_FALSE;
+
+ /* skip whitespace */
+ ch = ' ';
+
+ /* status = getChar(fd, headers+pos);
+ if (status == PR_FAILURE) {
+ goto bail;
+ }
+ ch = (char)headers[pos];
+ pos++; */
+
+ while(isWhiteSpace(ch)) {
+ status = getChar(fd, headers+pos);
+ if (status == PR_FAILURE) {
+ /* Error : */
+ goto bail;
+ }
+ ch = (char)headers[pos];
+ pos++;
+ }
+ value[index++] = ch;
+ } else {
+ value[index++] = ch;
+ }
+ break;
+ case '\r':
+ if (inName && !atEOL) {
+ return PR_FALSE;
+ }
+ break;
+ case '\n':
+ if (atEOL) {
+ doneParsing = PR_TRUE;
+ break;
+ }
+ if (inName) {
+ return PR_FALSE;
+ }
+ value[index] = '\0';
+ index = 0;
+ inName = PR_TRUE;
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> name=[%s] value=[%s]\n",name,value,0);
+ if (isRedirect && !PL_strcasecmp(name,"location")) {
+ location = PL_strdup(value);
+ }
+ atEOL = PR_TRUE;
+ break;
+ default:
+ atEOL = PR_FALSE;
+ if (inName) {
+ name[index++] = ch;
+ } else {
+ value[index++] = ch;
+ }
+ break;
+ }
+ }
+
+ if (!isRedirect) {
+ getBody(fd, resBUF, bytesRead);
+ } else {
+ *resBUF = PL_strdup(location);
+ *bytesRead = strlen(location);
+ }
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Response Buffer=[%s] bytesRead=[%d] \n",*resBUF,*bytesRead,0);
+
+bail:
+
+ if (headers) {
+ PR_Free(headers);
+ }
+ if (protocol) {
+ PL_strfree(protocol);
+ }
+ if (statusNum) {
+ PL_strfree(statusNum);
+ }
+ if (statusString) {
+ PL_strfree(statusString);
+ }
+ if (location) {
+ PL_strfree(location);
+ }
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- processResponse -- END\n",0,0,0);
+ return status;
+}
+
+static int nssReinitializationRequired()
+{
+ int nssReinitializationRequired = 0;
+ int err = 0;
+ int str_len = 0;
+ float version = 0;
+ const float DSVERSION = 6.1;
+ char *str = NULL;
+ char *value = NULL;
+ char *ver_value = NULL;
+ Slapi_Entry **entry = NULL;
+ Slapi_PBlock *resultpb= NULL;
+
+ resultpb= slapi_search_internal( "", LDAP_SCOPE_BASE, "objectclass=*", NULL, NULL, 0);
+ slapi_pblock_get( resultpb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entry );
+ slapi_pblock_get( resultpb, SLAPI_PLUGIN_INTOP_RESULT, &err);
+ if ( err == LDAP_SUCCESS && entry!=NULL && entry[0]!=NULL)
+ {
+ value = slapi_entry_attr_get_charptr(entry[0], "vendorVersion");
+ if (value == NULL || strncmp(value, "Netscape", strlen("Netscape")))
+ {
+ slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM,
+ "nssReinitializationRequired: vendor is not Netscape \n");
+ slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM,
+ "or version is earlier than 6.0\n", value);
+ nssReinitializationRequired = 1;
+ slapi_free_search_results_internal(resultpb);
+ slapi_pblock_destroy(resultpb);
+ slapi_ch_free((void **)&value);
+ return nssReinitializationRequired;
+ }
+
+ if ( (str = strstr(value,"/")) != NULL )
+ {
+ str++;
+ version = atof(str);
+ slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM,
+ "nssReinitializationRequired: version is %f. \n", version);
+ }
+
+
+ if (str == NULL || version < DSVERSION)
+ {
+ nssReinitializationRequired = 1;
+ }
+ slapi_ch_free((void **)&value);
+
+ }
+ slapi_free_search_results_internal(resultpb);
+ slapi_pblock_destroy(resultpb);
+ return nssReinitializationRequired;
+
+}
+
+static PRStatus sendGetReq(PRFileDesc *fd, const char *path)
+{
+ PRStatus status = PR_SUCCESS;
+ char *reqBUF = NULL;
+ PRInt32 http_connection_time_out = 0;
+ int buflen = (HTTP_GET_STD_LEN + strlen(path));
+
+ reqBUF = (char *)PR_Calloc(1, buflen);
+
+ strcpy(reqBUF, HTTP_GET);
+ strcat(reqBUF, " ");
+ strcat(reqBUF, path);
+ strcat(reqBUF, " ");
+ strcat(reqBUF, HTTP_PROTOCOL);
+ strcat(reqBUF, "\r\n\r\n\0");
+
+ http_connection_time_out = httpConfig->connectionTimeOut;
+ status = sendFullData( fd, reqBUF, http_connection_time_out);
+
+bail:
+ if (reqBUF) {
+ PR_Free(reqBUF);
+ reqBUF = 0;
+ }
+ return status;
+}
+
+static PRStatus sendFullData( PRFileDesc *fd, char *buf, int timeOut)
+{
+ int dataSent = 0;
+ int bufLen = strlen(buf);
+ int retVal = 0;
+ PRInt32 errcode = 0;
+ while (dataSent < bufLen)
+ {
+ retVal = PR_Send(fd, buf+dataSent, bufLen-dataSent, 0, PR_MillisecondsToInterval(timeOut));
+ if (retVal == -1 )
+ break;
+ dataSent += retVal;
+ }
+ if (dataSent == bufLen )
+ return PR_SUCCESS;
+ else
+ {
+ errcode = PR_GetError();
+ slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM,
+ "sendFullData: dataSent=%d bufLen=%d -> NSPR Error code (%d)\n",
+ dataSent, bufLen, errcode);
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "---------->NSPR Error code (%d) \n", errcode,0,0);
+ return PR_FAILURE;
+ }
+}
+
+static PRStatus sendPostReq(PRFileDesc *fd, const char *path, httpheader **httpheaderArray, char *body)
+{
+ PRStatus status = PR_SUCCESS;
+ char body_len_str[20];
+ char *reqBUF = NULL;
+ PRInt32 http_connection_time_out = 0;
+ int i = 0;
+ int body_len, buflen = 0;
+
+ body_len = strlen(body);
+ PR_snprintf(body_len_str, sizeof(body_len_str), "%d", body_len);
+
+ buflen = (HTTP_POST_STD_LEN + strlen(path) + body_len + strlen(body_len_str));
+
+ for (i = 0; httpheaderArray[i] != NULL; i++) {
+
+ if (httpheaderArray[i]->name != NULL)
+ {
+ buflen += strlen(httpheaderArray[i]->name) + 2;
+ if (httpheaderArray[i]->value != NULL)
+ buflen += strlen(httpheaderArray[i]->value) + 2;
+ }
+
+ }
+
+ reqBUF = (char *)PR_Calloc(1, buflen);
+
+ strcpy(reqBUF, HTTP_POST);
+ strcat(reqBUF, " ");
+ strcat(reqBUF, path);
+ strcat(reqBUF, " ");
+ strcat(reqBUF, HTTP_PROTOCOL);
+ strcat(reqBUF, "\r\n");
+ strcat(reqBUF, HTTP_CONTENT_LENGTH);
+ strcat(reqBUF, " ");
+ strcat(reqBUF, body_len_str);
+ strcat(reqBUF, "\r\n");
+ strcat(reqBUF, HTTP_CONTENT_TYPE_URL_ENCODED);
+ strcat(reqBUF, "\r\n");
+
+ for (i = 0; httpheaderArray[i] != NULL; i++) {
+
+ if (httpheaderArray[i]->name != NULL)
+ strcat(reqBUF, httpheaderArray[i]->name);
+ strcat(reqBUF, ": ");
+ if (httpheaderArray[i]->value != NULL)
+ strcat(reqBUF, httpheaderArray[i]->value);
+ strcat(reqBUF, "\r\n");
+
+ }
+
+ strcat(reqBUF, "\r\n");
+ strcat(reqBUF, body);
+ strcat(reqBUF, "\0");
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "---------->reqBUF is %s \n",reqBUF,0,0);
+ http_connection_time_out = httpConfig->connectionTimeOut;
+
+ status = sendFullData( fd, reqBUF, http_connection_time_out);
+
+bail:
+ if (reqBUF) {
+ PR_Free(reqBUF);
+ reqBUF = 0;
+ }
+ return status;
+}
+
+
+static PRStatus getChar(PRFileDesc *fd, char *buf)
+{
+ PRInt32 bytesRead = http_read(fd, buf, 1);
+ if (bytesRead <=0) {
+ PRInt32 errcode = PR_GetError();
+ slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM,
+ "getChar: NSPR Error code (%d)\n", errcode);
+ return PR_FAILURE;
+ }
+ return PR_SUCCESS;
+}
+
+static PRStatus getBody(PRFileDesc *fd, char **buf, int *actualBytesRead)
+{
+ int totalBytesRead = 0;
+ int size = 4 * HTTP_DEFAULT_BUFFER_SIZE;
+ int bytesRead = size;
+ char *data = (char *) PR_Calloc(1, size);
+ while (bytesRead == size) {
+ bytesRead = http_read(fd, (data+totalBytesRead), size);
+ if (bytesRead <= 0) {
+ /* Read error */
+ return PR_FAILURE;
+ }
+ if (bytesRead == size) {
+ /* more data to be read so increase the buffer */
+ size = size * 2 ;
+ data = (char *) PR_Realloc(data, size);
+ }
+ totalBytesRead += bytesRead;
+ }
+ *buf = data;
+ *actualBytesRead = totalBytesRead;
+
+ return PR_SUCCESS;
+}
+
+static PRInt32 http_read(PRFileDesc *fd, char *buf, int size)
+{
+ PRInt32 http_read_time_out = 0;
+ http_read_time_out = httpConfig->readTimeOut;
+ return PR_Recv(fd, buf, size, 0, PR_MillisecondsToInterval(http_read_time_out));
+}
+
+static PRBool isWhiteSpace(char ch)
+{
+ PRBool b = PR_FALSE;
+ if (ch == ' ') {
+ b = PR_TRUE;
+ }
+ return b;
+}
+
+static PRStatus parseURI(const char *urlstr, char **host, PRInt32 *port, char **path, int *sslOn)
+{
+ PRStatus status = PR_SUCCESS;
+ char *brk;
+ int len;
+ static const char delimiters[] = ":/?#";
+ char *url = isHttpReq(urlstr, sslOn);
+
+ if (*sslOn) {
+ *port = 443;
+ }
+ else {
+ *port = 80;
+ }
+ if (url == NULL) {
+ /* Error : */
+ status = PR_FAILURE;
+ goto bail;
+ }
+ len = PL_strlen(url);
+ /* Currently we do not support Ipv6 addresses */
+ brk = PL_strpbrk(url, delimiters);
+ if (!brk) {
+ *host = PL_strndup(url, len);
+ toLowerCase(*host);
+ goto bail;
+ }
+ switch (*brk)
+ {
+ case '/' :
+ case '?' :
+ case '#' :
+ /* Get the Host, the rest is Path */
+ *host = PL_strndup(url, (brk - url));
+ toLowerCase(*host);
+ status = parseAtPath(brk, path);
+ break;
+ case ':' :
+ /* Get the Host and process port, path */
+ *host = PL_strndup(url, (brk - url));
+ toLowerCase(*host);
+ status = parseAtPort(brk+1, port, path);
+ break;
+ default:
+ /* Error : HTTP_BAD_URL */
+ break;
+ }
+
+bail:
+ if (url) {
+ PR_Free(url);
+ }
+ return status;
+}
+
+static PRStatus parseAtPort(const char* url, PRInt32 *port, char **path)
+{
+ PRStatus status = PR_SUCCESS;
+ static const char delimiters[] = "/?#";
+ char* brk = PL_strpbrk(url, delimiters);
+ if (!brk) /* everything is a Port */
+ {
+ *port = getPort(url);
+ if (*port <= 0) {
+ /* Error : HTTP_BAD_URL */
+ return PR_FAILURE;
+ } else {
+ return status;
+ }
+ }
+
+ switch (*brk)
+ {
+ case '/' :
+ case '?' :
+ case '#' :
+ /* Get the Port, the rest is Path */
+ *port = getPort(url);
+ if (*port <= 0) {
+ /* Error : HTTP_BAD_URL */
+ return PR_FAILURE;
+ }
+ status = parseAtPath(brk, path);
+ break;
+ default:
+ /* Error : HTTP_BAD_URL */
+ break;
+ }
+ return status;
+}
+
+static PRStatus parseAtPath(const char *url, char **path)
+{
+ PRStatus status = PR_SUCCESS;
+ char *dir = "%s%s";
+ *path = (char *)PR_Calloc(1, (strlen(dir) + 1024));
+
+ /* Just write the path and check for a starting / */
+ if ('/' != *url) {
+ PR_sscanf(*path, dir, "/", url);
+ } else {
+ strcpy(*path, url);
+ }
+ if (!*path) {
+ /* Error : HTTP_BAD_URL */
+ status = PR_FAILURE;
+ }
+ return status;
+}
+
+static void toLowerCase(char* str)
+{
+ if (str) {
+ char* lstr = str;
+ PRInt8 shift = 'a' - 'A';
+ for(; (*lstr != '\0'); ++lstr) {
+ if ((*(lstr) <= 'Z') && (*(lstr) >= 'A')) {
+ *(lstr) = *(lstr) + shift;
+ }
+ }
+ }
+}
+
+static PRInt32 getPort(const char* src)
+{
+ /* search for digits up to a slash or the string ends */
+ const char* port = src;
+ PRInt32 returnValue = -1;
+ char c;
+
+ /* skip leading white space */
+ while (isAsciiSpace(*port))
+ port++;
+
+ while ((c = *port++) != '\0') {
+ /* stop if slash or ? or # reached */
+ if (c == '/' || c == '?' || c == '#')
+ break;
+ else if (!isAsciiDigit(c))
+ return returnValue;
+ }
+ return (0 < PR_sscanf(src, "%d", &returnValue)) ? returnValue : -1;
+}
+
+
+static PRBool isAsciiSpace(char aChar)
+{
+ if ((aChar == ' ') || (aChar == '\r') || (aChar == '\n') || (aChar == '\t')) {
+ return PR_TRUE;
+ }
+ return PR_FALSE;
+}
+
+static PRBool isAsciiDigit(char aChar)
+{
+ if ((aChar >= '0') && (aChar <= '9')) {
+ return PR_TRUE;
+ }
+ return PR_FALSE;
+}
+
+static void setTCPNoDelay(PRFileDesc* fd)
+{
+ PRStatus status = PR_SUCCESS;
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_NoDelay;
+ opt.value.no_delay = PR_FALSE;
+
+ status = PR_GetSocketOption(fd, &opt);
+ if (status == PR_FAILURE) {
+ return;
+ }
+
+ opt.option = PR_SockOpt_NoDelay;
+ opt.value.no_delay = PR_TRUE;
+ status = PR_SetSocketOption(fd, &opt);
+ if (status == PR_FAILURE) {
+ return;
+ }
+ return;
+}
+
+static char * isHttpReq(const char *url, int *sslOn)
+{
+ static const char http_protopol_header[] = "http://";
+ static const char https_protopol_header[] = "https://";
+ char *newstr = NULL;
+ /* skip leading white space */
+ while (isAsciiSpace(*url))
+ url++;
+
+ if (strncmp(url, http_protopol_header, strlen(http_protopol_header)) == 0) {
+ newstr = (char *)PR_Calloc(1, (strlen(url)-strlen(http_protopol_header) + 1));
+ strcpy(newstr, url+7);
+ strcat(newstr,"\0");
+ *sslOn = 0;
+ }
+ else if (strncmp(url, https_protopol_header, strlen(https_protopol_header)) == 0) {
+ newstr = (char *)PR_Calloc(1, (strlen(url)-strlen(https_protopol_header) + 1));
+ strcpy(newstr, url+8);
+ strcat(newstr,"\0");
+ *sslOn = 1;
+ }
+
+ return newstr;
+}
+
+PRFileDesc* setupSSLSocket(PRFileDesc* fd)
+{
+ SECStatus secStatus;
+ PRFileDesc* sslSocket;
+ PRSocketOptionData socketOption;
+ char *certNickname = NULL;
+
+ socketOption.option = PR_SockOpt_Nonblocking;
+ socketOption.value.non_blocking = PR_FALSE;
+ if( PR_SetSocketOption(fd, &socketOption) != 0) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM,
+ "Cannot set socket option NSS \n");
+ return NULL;
+ }
+
+ sslSocket = SSL_ImportFD(NULL, fd);
+ if (!sslSocket) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM,
+ "setupSSLSocket: Cannot import to SSL Socket\n" );
+ goto sslbail;
+ }
+
+ slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM,
+ "setupSSLSocket: setupssl socket created\n" );
+
+ secStatus = SSL_OptionSet(sslSocket, SSL_SECURITY, 1);
+ if (SECSuccess != secStatus) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM,
+ "setupSSLSocket: Cannot set SSL_SECURITY option\n");
+ goto sslbail;
+ }
+
+ secStatus = SSL_OptionSet(sslSocket, SSL_HANDSHAKE_AS_CLIENT, 1);
+ if (SECSuccess != secStatus) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM,
+ "setupSSLSocket: CAnnot set SSL_HANDSHAKE_AS_CLIENT option\n");
+ goto sslbail;
+ }
+
+ /* Set SSL callback routines. */
+
+ secStatus = SSL_GetClientAuthDataHook(sslSocket,
+ (SSLGetClientAuthData) getClientAuthData,
+ (void *)certNickname);
+ if (secStatus != SECSuccess) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM,
+ "setupSSLSocket: SSL_GetClientAuthDataHook Failed\n");
+ goto sslbail;
+ }
+
+ secStatus = SSL_AuthCertificateHook(sslSocket,
+ (SSLAuthCertificate) authCertificate,
+ (void *)CERT_GetDefaultCertDB());
+ if (secStatus != SECSuccess) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM,
+ "setupSSLSocket: SSL_AuthCertificateHook Failed\n");
+ goto sslbail;
+ }
+
+ secStatus = SSL_BadCertHook(sslSocket,
+ (SSLBadCertHandler) badCertHandler, NULL);
+ if (secStatus != SECSuccess) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM,
+ "setupSSLSocket: SSL_BadCertHook Failed\n");
+ goto sslbail;
+ }
+
+ secStatus = SSL_HandshakeCallback(sslSocket,
+ (SSLHandshakeCallback) handshakeCallback, NULL);
+ if (secStatus != SECSuccess) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM,
+ "setupSSLSocket: SSL_HandshakeCallback Failed\n");
+ goto sslbail;
+ }
+
+ return sslSocket;
+
+sslbail:
+ PR_Close(fd);
+ return NULL;
+}
+
+SECStatus
+ authCertificate(void *arg, PRFileDesc *socket,
+ PRBool checksig, PRBool isServer)
+{
+
+ SECCertUsage certUsage;
+ CERTCertificate * cert;
+ void * pinArg;
+ char * hostName;
+ SECStatus secStatus;
+
+ if (!arg || !socket) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM,
+ " authCertificate: Faulty socket in callback function \n");
+ return SECFailure;
+ }
+
+ /* Define how the cert is being used based upon the isServer flag. */
+
+ certUsage = isServer ? certUsageSSLClient : certUsageSSLServer;
+
+ cert = SSL_PeerCertificate(socket);
+
+ pinArg = SSL_RevealPinArg(socket);
+
+ secStatus = CERT_VerifyCertNow((CERTCertDBHandle *)arg,
+ cert,
+ checksig,
+ certUsage,
+ pinArg);
+
+ /* If this is a server, we're finished. */
+ if (isServer || secStatus != SECSuccess) {
+ return secStatus;
+ }
+
+ hostName = SSL_RevealURL(socket);
+
+ if (hostName && hostName[0]) {
+ secStatus = CERT_VerifyCertName(cert, hostName);
+ } else {
+ PR_SetError(SSL_ERROR_BAD_CERT_DOMAIN, 0);
+ secStatus = SECFailure;
+ }
+
+ if (hostName)
+ PR_Free(hostName);
+
+ return secStatus;
+}
+
+SECStatus
+ badCertHandler(void *arg, PRFileDesc *socket)
+{
+
+ SECStatus secStatus = SECFailure;
+ PRErrorCode err;
+
+ /* log invalid cert here */
+
+ if (!arg) {
+ return secStatus;
+ }
+
+ *(PRErrorCode *)arg = err = PORT_GetError();
+ switch (err) {
+ case SEC_ERROR_INVALID_AVA:
+ case SEC_ERROR_INVALID_TIME:
+ case SEC_ERROR_BAD_SIGNATURE:
+ case SEC_ERROR_EXPIRED_CERTIFICATE:
+ case SEC_ERROR_UNKNOWN_ISSUER:
+ case SEC_ERROR_UNTRUSTED_CERT:
+ case SEC_ERROR_CERT_VALID:
+ case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
+ case SEC_ERROR_CRL_EXPIRED:
+ case SEC_ERROR_CRL_BAD_SIGNATURE:
+ case SEC_ERROR_EXTENSION_VALUE_INVALID:
+ case SEC_ERROR_CA_CERT_INVALID:
+ case SEC_ERROR_CERT_USAGES_INVALID:
+ case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION:
+ secStatus = SECSuccess;
+ break;
+ default:
+ secStatus = SECFailure;
+ break;
+ }
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM,
+ "Bad certificate: %d\n", err);
+
+ return secStatus;
+}
+
+SECStatus
+ getClientAuthData(void *arg,
+ PRFileDesc *socket,
+ struct CERTDistNamesStr *caNames,
+ struct CERTCertificateStr **pRetCert,
+ struct SECKEYPrivateKeyStr **pRetKey)
+{
+ CERTCertificate * cert;
+ SECKEYPrivateKey * privKey;
+ char * chosenNickName = (char *)arg;
+ void * proto_win = NULL;
+ SECStatus secStatus = SECFailure;
+ proto_win = SSL_RevealPinArg(socket);
+
+ if (chosenNickName) {
+ cert = PK11_FindCertFromNickname(chosenNickName, proto_win);
+ if (cert) {
+ privKey = PK11_FindKeyByAnyCert(cert, proto_win);
+ if (privKey) {
+ secStatus = SECSuccess;
+ } else {
+ CERT_DestroyCertificate(cert);
+ }
+ }
+ } else { /* no nickname given, automatically find the right cert */
+ CERTCertNicknames *names;
+ int i;
+
+ names = CERT_GetCertNicknames(CERT_GetDefaultCertDB(),
+ SEC_CERT_NICKNAMES_USER, proto_win);
+
+ if (names != NULL) {
+ for(i = 0; i < names->numnicknames; i++ ) {
+
+ cert = PK11_FindCertFromNickname(names->nicknames[i],
+ proto_win);
+ if (!cert) {
+ continue;
+ }
+
+ /* Only check unexpired certs */
+ if (CERT_CheckCertValidTimes(cert, PR_Now(), PR_FALSE)
+ != secCertTimeValid ) {
+ CERT_DestroyCertificate(cert);
+ continue;
+ }
+
+ secStatus = NSS_CmpCertChainWCANames(cert, caNames);
+ if (secStatus == SECSuccess) {
+ privKey = PK11_FindKeyByAnyCert(cert, proto_win);
+ if (privKey) {
+ break;
+ }
+ secStatus = SECFailure;
+ break;
+ }
+ CERT_FreeNicknames(names);
+ } /* for loop */
+ }
+ }
+
+ if (secStatus == SECSuccess) {
+ *pRetCert = cert;
+ *pRetKey = privKey;
+ }
+
+ return secStatus;
+}
+
+SECStatus
+ handshakeCallback(PRFileDesc *socket, void *arg)
+{
+ slapi_log_error(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM,
+ "----------> Handshake has completed, ready to send data securely.\n");
+ return SECSuccess;
+}
+
+
+/**
+ * PUBLIC FUNCTIONS IMPLEMENTATION
+ */
+int http_impl_init(Slapi_ComponentId *plugin_id)
+{
+ int status = HTTP_IMPL_SUCCESS;
+ slapi_log_error(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM,
+ "-> http_impl_init \n");
+ httpConfig = NULL;
+
+ httpConfig = (httpPluginConfig *) slapi_ch_calloc(1, sizeof(httpPluginConfig));
+
+ status = readConfigLDAPurl(plugin_id, HTTP_PLUGIN_DN);
+ if (status != 0) {
+ slapi_log_error(SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM,
+ "http_impl_start: Unable to get HTTP config information \n");
+ return HTTP_IMPL_FAILURE;
+ }
+
+ status = readConfigLDAPurl(plugin_id, CONFIG_DN);
+ if (status != 0) {
+ slapi_log_error(SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM,
+ "http_impl_start: Unable to get config information \n");
+ return HTTP_IMPL_FAILURE;
+ }
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM,
+ "<- http_impl_init \n");
+
+ return status;
+
+}
+
+
+int http_impl_get_text(char *url, char **data, int *bytesRead)
+{
+ int status = HTTP_IMPL_SUCCESS;
+ status = doRequestRetry(url, NULL, NULL, data, bytesRead, HTTP_REQ_TYPE_GET);
+ return status;
+}
+
+int http_impl_get_binary(char *url, char **data, int *bytesRead)
+{
+ int status = HTTP_IMPL_SUCCESS;
+ status = doRequestRetry(url, NULL, NULL, data, bytesRead, HTTP_REQ_TYPE_GET);
+ return status;
+}
+
+int http_impl_get_redirected_uri(char *url, char **data, int *bytesRead)
+{
+ int status = HTTP_IMPL_SUCCESS;
+ status = doRequestRetry(url, NULL, NULL, data, bytesRead, HTTP_REQ_TYPE_REDIRECT);
+ return status;
+}
+
+int http_impl_post(char *url, httpheader **httpheaderArray, char *body, char **data, int *bytesRead)
+{
+ int status = HTTP_IMPL_SUCCESS;
+ status = doRequestRetry(url, httpheaderArray, body, data, bytesRead, HTTP_REQ_TYPE_POST);
+ return status;
+}
+
+void http_impl_shutdown()
+{
+ int status = HTTP_IMPL_SUCCESS;
+ /**
+ * Put cleanup code here
+ */
+}
+
+static int readConfigLDAPurl(Slapi_ComponentId *plugin_id, char *plugindn) {
+
+ int rc = LDAP_SUCCESS;
+ Slapi_DN *sdn = NULL;
+ int status = HTTP_IMPL_SUCCESS;
+ Slapi_Entry *entry = NULL;
+
+ sdn = slapi_sdn_new_dn_byref(plugindn);
+ rc = slapi_search_internal_get_entry(sdn, NULL, &entry, plugin_id);
+ slapi_sdn_free(&sdn);
+ if (rc != LDAP_SUCCESS) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM,
+ "readConfigLDAPurl: Could not find entry %s (error %d)\n", plugindn, rc);
+ status = HTTP_IMPL_FAILURE;
+ return status;
+ }
+ if (NULL == entry)
+ {
+ slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM,
+ "readConfigLDAPurl: No entries found for <%s>\n", plugindn);
+
+ status = HTTP_IMPL_FAILURE;
+ return status;
+ }
+
+ if ((PL_strcasecmp(plugindn, HTTP_PLUGIN_DN) == 0))
+ status = parseHTTPConfigEntry(entry);
+ else
+ status = parseConfigEntry(entry);
+
+ slapi_entry_free(entry);
+ return status;
+
+}
+
+/* Retrieves the plugin configuration info */
+
+/* Retrieves security info as well as the path info required for the SSL
+config dir */
+static int parseConfigEntry(Slapi_Entry *e)
+{
+ char *value = NULL;
+
+ value = slapi_entry_attr_get_charptr(e, ATTR_DS_SECURITY);
+ if (value) {
+ httpConfig->DS_sslOn = value;
+ }
+
+ return HTTP_IMPL_SUCCESS;
+
+}
+
+
+static int parseHTTPConfigEntry(Slapi_Entry *e)
+{
+ int value = 0;
+
+
+ value = slapi_entry_attr_get_int(e, ATTR_RETRY_COUNT);
+ if (value) {
+ httpConfig->retryCount = value;
+ }
+
+ value = slapi_entry_attr_get_int(e, ATTR_CONNECTION_TIME_OUT);
+ if (value) {
+ httpConfig->connectionTimeOut = value;
+ }
+ else {
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "parseHTTPConfigEntry: HTTP Connection Time Out cannot be read. Setting to default value of 5000 ms \n", 0,0,0);
+ httpConfig->connectionTimeOut = 5000;
+ }
+
+
+ value = slapi_entry_attr_get_int(e, ATTR_READ_TIME_OUT);
+ if (value) {
+ httpConfig->readTimeOut = value;
+ }
+ else {
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "parseHTTPConfigEntry: HTTP Read Time Out cannot be read. Setting to default value of 5000 ms \n", 0,0,0);
+ httpConfig->readTimeOut = 5000;
+ }
+
+ httpConfig->nssInitialized = 0;
+
+ return HTTP_IMPL_SUCCESS;
+
+}
+
+/**
+ * Self Testing
+ */
+#ifdef BUILD_STANDALONE
+int main(int argc, char **argv)
+{
+ PRStatus status = PR_SUCCESS;
+ char *buf;
+ int bytes;
+ char *host;
+ PRInt32 port;
+ char *path;
+ if (argc < 2) {
+ printf("URL missing\n");
+ return -1;
+ }
+ PR_Init(PR_USER_THREAD,PR_PRIORITY_NORMAL, 0);
+ doRequest(argv[1], &buf, &bytes, 2);
+ printf( "%s\n", buf );
+ return -1;
+}
+#endif
+
diff --git a/ldap/servers/plugins/http/http_impl.h b/ldap/servers/plugins/http/http_impl.h
new file mode 100644
index 00000000..0bca3ca2
--- /dev/null
+++ b/ldap/servers/plugins/http/http_impl.h
@@ -0,0 +1,25 @@
+/**
+ * PROPRIETARY/CONFIDENTIAL. Use of this product is subject to
+ * license terms. Copyright 2001 Sun Microsystems, Inc.
+ * Some preexisting portions Copyright 2001 Netscape Communications Corp.
+ * All rights reserved.
+ */
+#ifndef HTTP_IMPL_H__
+#define HTTP_IMPL_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int http_impl_init(Slapi_ComponentId *plugin_id);
+int http_impl_get_text(char *url, char **data, int *bytesRead);
+int http_impl_get_binary(char *url, char **data, int *bytesRead);
+int http_impl_get_redirected_uri(char *url, char **data, int *bytesRead);
+int http_impl_post(char *url, httpheader **httpheaderArray, char *body, char **data, int *bytesRead);
+void http_impl_shutdown();
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/ldap/servers/plugins/passthru/Makefile b/ldap/servers/plugins/passthru/Makefile
new file mode 100644
index 00000000..11540915
--- /dev/null
+++ b/ldap/servers/plugins/passthru/Makefile
@@ -0,0 +1,90 @@
+#
+# BEGIN COPYRIGHT BLOCK
+# Copyright 2001 Sun Microsystems, Inc.
+# Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+# All rights reserved.
+# END COPYRIGHT BLOCK
+#
+#
+# GNU Makefile for Directory Server "Pass Through Authentication" plugin
+#
+#
+
+LDAP_SRC = ../../..
+MCOM_ROOT = ../../../../..
+
+NOSTDCLEAN=true # don't let nsconfig.mk define target clean
+NOSTDSTRIP=true # don't let nsconfig.mk define target strip
+NSPR20=true # probably should be defined somewhere else (not sure where)
+
+OBJDEST = $(OBJDIR)/lib/libpassthru
+LIBDIR = $(LIB_RELDIR)
+
+include $(MCOM_ROOT)/ldapserver/nsconfig.mk
+include $(LDAP_SRC)/nsldap.mk
+
+ifeq ($(ARCH), WINNT)
+DEF_FILE:=./libpassthru.def
+endif
+
+CFLAGS+=$(SLCFLAGS)
+
+INCLUDES += -I$(LDAP_SRC)/servers/slapd
+
+PASSTHRU_OBJS= ptbind.o ptconfig.o ptconn.o ptdebug.o ptpreop.o ptutil.o
+
+OBJS = $(addprefix $(OBJDEST)/, $(PASSTHRU_OBJS))
+
+ifeq ($(ARCH), WINNT)
+LIBPASSTHRU_DLL_OBJ = $(addprefix $(OBJDEST)/, ptdllmain.o)
+endif
+
+LIBPASSTHRU= $(addprefix $(LIBDIR)/, $(PASSTHRU_DLL).$(DLL_SUFFIX))
+
+ifeq ($(ARCH), WINNT)
+EXTRA_LIBS_DEP += $(LIBSLAPD_DEP)
+EXTRA_LIBS_DEP += $(LDAPSDK_DEP) $(NSPR_DEP)
+EXTRA_LIBS += $(LIBSLAPD) $(LDAP_SDK_LIBLDAP_DLL) $(NSPRLINK)
+endif
+
+
+ifeq ($(ARCH), WINNT)
+DLL_LDFLAGS += -def:"./libpassthru.def"
+endif # WINNT
+
+ifeq ($(ARCH), AIX)
+EXTRA_LIBS_DEP += $(LIBSLAPD_DEP)
+EXTRA_LIBS_DEP += $(LDAPSDK_DEP) $(NSPR_DEP)
+EXTRA_LIBS += $(LIBSLAPDLINK) $(LDAP_SDK_LIBLDAP_DLL) $(NSPRLINK)
+EXTRA_LIBS += $(DLL_EXTRA_LIBS)
+LD=ld
+endif
+
+ifeq ($(ARCH), HPUX)
+EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAPSDK_DEP) $(NSPR_DEP) $(SECURITY_DEP)
+EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(SECURITYLINK) $(NSPRLINK) $(ICULINK)
+endif
+
+clientSDK:
+
+all: $(OBJDEST) $(LIBDIR) $(LIBPASSTHRU)
+
+$(LIBPASSTHRU): $(OBJS) $(LIBPASSTHRU_DLL_OBJ) $(DEF_FILE)
+ $(LINK_DLL) $(LIBPASSTHRU_DLL_OBJ) $(PLATFORMLIBS) $(EXTRA_LIBS)
+
+veryclean: clean
+
+clean:
+ $(RM) $(OBJS)
+ifeq ($(ARCH), WINNT)
+ $(RM) $(LIBPASSTHRU_DLL_OBJ)
+endif
+ $(RM) $(LIBPASSTHRU)
+
+$(OBJDEST):
+ $(MKDIR) $(OBJDEST)
+
+#
+# header file dependencies (incomplete)
+#
+$(OBJS): passthru.h
diff --git a/ldap/servers/plugins/passthru/PT-Notes b/ldap/servers/plugins/passthru/PT-Notes
new file mode 100644
index 00000000..2e3cea10
--- /dev/null
+++ b/ldap/servers/plugins/passthru/PT-Notes
@@ -0,0 +1,30 @@
+#
+# BEGIN COPYRIGHT BLOCK
+# Copyright 2001 Sun Microsystems, Inc.
+# Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+# All rights reserved.
+# END COPYRIGHT BLOCK
+#
+ Pass Through Authentication Plugin Notes
+
+Key
+ r required feature
+ n nice-to-have
+ ? undecided whether this is a good idea or not
+
+Missing features:
+n U/I for configuration.
+
+Loose ends:
+n Resolve any remaining code that is marked with XXX.
+n Put some thought into cases we do not handle (SASL, no DN, no passwd) and
+ make sure we do the right thing in terms of errors, letting server's
+ standard mechanism handle the bind, etc.
+? Protect against server connecting back to itself recursively.
+
+Testing:
+r Basic tests (all platforms: NT,Sol,IRIX,AIX,HP/UX,OSF/1).
+r Controls (both coming (e.g., ?) and going (e.g., password policy).
+r SSL connections to remote servers.
+r LDAPv2/v3 compatiblity
+r Stress tests.
diff --git a/ldap/servers/plugins/passthru/libpassthru.def b/ldap/servers/plugins/passthru/libpassthru.def
new file mode 100644
index 00000000..b08d907a
--- /dev/null
+++ b/ldap/servers/plugins/passthru/libpassthru.def
@@ -0,0 +1,14 @@
+; BEGIN COPYRIGHT BLOCK
+; Copyright 2001 Sun Microsystems, Inc.
+; Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+; All rights reserved.
+; END COPYRIGHT BLOCK
+;
+;
+;
+DESCRIPTION 'Netscape Directory Server 7 Pass Through Authentication Plugin'
+;CODE SHARED READ EXECUTE
+;DATA SHARED READ WRITE
+EXPORTS
+ passthruauth_init @1
+ plugin_init_debug_level @2
diff --git a/ldap/servers/plugins/passthru/passthru.h b/ldap/servers/plugins/passthru/passthru.h
new file mode 100644
index 00000000..fdf30d65
--- /dev/null
+++ b/ldap/servers/plugins/passthru/passthru.h
@@ -0,0 +1,131 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/*
+ * passthru.h - Pass Through Authentication shared definitions
+ *
+ */
+
+#ifndef _PASSTHRU_H_
+#define _PASSTHRU_H_
+
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <sys/types.h>
+#include "portable.h"
+#include "slapi-plugin.h"
+#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */
+#include "dirver.h"
+#include <nspr.h>
+
+/* Private API: to get slapd_pr_strerror() and SLAPI_COMPONENT_NAME_NSPR */
+#include "slapi-private.h"
+
+/*
+ * macros
+ */
+#define PASSTHRU_PLUGIN_SUBSYSTEM "passthru-plugin" /* for logging */
+
+#define PASSTHRU_ASSERT( expr ) PR_ASSERT( expr )
+
+#define PASSTHRU_LDAP_CONN_ERROR( err ) ( (err) == LDAP_SERVER_DOWN || \
+ (err) == LDAP_CONNECT_ERROR )
+
+#define PASSTHRU_OP_NOT_HANDLED 0
+#define PASSTHRU_OP_HANDLED 1
+
+#define PASSTHRU_CONN_TRIES 2
+
+/* #define PASSTHRU_VERBOSE_LOGGING */
+
+/* defaults */
+#define PASSTHRU_DEF_SRVR_MAXCONNECTIONS 3
+#define PASSTHRU_DEF_SRVR_MAXCONCURRENCY 5
+#define PASSTHRU_DEF_SRVR_TIMEOUT 300 /* seconds */
+#define PASSTHRU_DEF_SRVR_PROTOCOL_VERSION LDAP_VERSION3
+#define PASSTHRU_DEF_SRVR_CONNLIFETIME 0 /* seconds */
+#define PASSTHRU_DEF_SRVR_FAILOVERCONNLIFETIME 300 /* seconds */
+
+/*
+ * structs
+ */
+typedef struct passthrusuffix {
+ int ptsuffix_len;
+ char *ptsuffix_normsuffix; /* not case normalized */
+ struct passthrusuffix *ptsuffix_next;
+} PassThruSuffix;
+
+typedef struct passthruconnection {
+ LDAP *ptconn_ld;
+ int ptconn_ldapversion;
+ int ptconn_usecount;
+#define PASSTHRU_CONNSTATUS_OK 0
+#define PASSTHRU_CONNSTATUS_DOWN 1
+#define PASSTHRU_CONNSTATUS_STALE 2
+ int ptconn_status;
+ time_t ptconn_opentime;
+ struct passthruconnection *ptconn_prev;
+ struct passthruconnection *ptconn_next;
+} PassThruConnection;
+
+typedef struct passthruserver {
+ char *ptsrvr_url; /* copy from argv[i] */
+ char *ptsrvr_hostname;
+ int ptsrvr_port;
+ int ptsrvr_secure; /* use SSL? */
+ int ptsrvr_ldapversion;
+ int ptsrvr_maxconnections;
+ int ptsrvr_maxconcurrency;
+ int ptsrvr_connlifetime; /* in seconds */
+ struct timeval *ptsrvr_timeout; /* for ldap_result() */
+ PassThruSuffix *ptsrvr_suffixes;
+ Slapi_CondVar *ptsrvr_connlist_cv;
+ Slapi_Mutex *ptsrvr_connlist_mutex; /* protects connlist */
+ int ptsrvr_connlist_count;
+ PassThruConnection *ptsrvr_connlist;
+ struct passthruserver *ptsrvr_next;
+} PassThruServer;
+
+typedef struct passthruconfig {
+ PassThruServer *ptconfig_serverlist;
+} PassThruConfig;
+
+
+/*
+ * public functions
+ */
+/*
+ * ptbind.c:
+ */
+int passthru_simple_bind_s( Slapi_PBlock *pb, PassThruServer *srvr, int tries,
+ char *dn, struct berval *creds, LDAPControl **reqctrls, int *lderrnop,
+ char **matcheddnp, char **errmsgp, struct berval ***refurlsp,
+ LDAPControl ***resctrlsp );
+
+/*
+ * ptconfig.c:
+ */
+int passthru_config( int argc, char **argv );
+PassThruConfig *passthru_get_config( void );
+
+/*
+ * ptconn.c:
+ */
+int passthru_dn2server( PassThruConfig *cfg, char *normdn,
+ PassThruServer **srvrp );
+int passthru_get_connection( PassThruServer *srvr, LDAP **ldp );
+void passthru_release_connection( PassThruServer *srvr, LDAP *ld, int dispose );
+void passthru_close_all_connections( PassThruConfig *cfg );
+
+/*
+ * ptutil.c:
+ */
+struct berval **passthru_strs2bervals( char **ss );
+char ** passthru_bervals2strs( struct berval **bvs );
+void passthru_free_bervals( struct berval **bvs );
+char *passthru_urlparse_err2string( int err );
+
+#endif /* _PASSTHRU_H_ */
diff --git a/ldap/servers/plugins/passthru/ptbind.c b/ldap/servers/plugins/passthru/ptbind.c
new file mode 100644
index 00000000..f9da57a1
--- /dev/null
+++ b/ldap/servers/plugins/passthru/ptbind.c
@@ -0,0 +1,144 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/*
+ * ptbind.c - LDAP bind-related code for Pass Through Authentication
+ *
+ */
+
+#include "passthru.h"
+
+static int
+passthru_simple_bind_once_s( PassThruServer *srvr, char *dn,
+ struct berval *creds, LDAPControl **reqctrls, int *lderrnop,
+ char **matcheddnp, char **errmsgp, struct berval ***refurlsp,
+ LDAPControl ***resctrlsp );
+
+
+/*
+ * Attempt to chain a bind request off to "srvr." We return an LDAP error
+ * code that indicates whether we successfully got a response from the
+ * other server or not. If we succeed, we return LDAP_SUCCESS and *lderrnop
+ * is set to the result code from the remote server.
+ *
+ * Note that in the face of "ldap server down" or "ldap connect failed" errors
+ * we make up to "tries" attempts to bind to the remote server. Since we
+ * are only interested in recovering silently when the remote server is up
+ * but decided to close our connection, we retry without pausing between
+ * attempts.
+ */
+int
+passthru_simple_bind_s( Slapi_PBlock *pb, PassThruServer *srvr, int tries,
+ char *dn, struct berval *creds, LDAPControl **reqctrls, int *lderrnop,
+ char **matcheddnp, char **errmsgp, struct berval ***refurlsp,
+ LDAPControl ***resctrlsp )
+{
+ int rc;
+
+ PASSTHRU_ASSERT( srvr != NULL );
+ PASSTHRU_ASSERT( tries > 0 );
+ PASSTHRU_ASSERT( creds != NULL );
+ PASSTHRU_ASSERT( lderrnop != NULL );
+ PASSTHRU_ASSERT( refurlsp != NULL );
+
+ do {
+ /*
+ * check to see if operation has been abandoned...
+ */
+ if ( slapi_op_abandoned( pb )) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM,
+ "operation abandoned\n" );
+ rc = LDAP_USER_CANCELLED;
+ } else {
+ rc = passthru_simple_bind_once_s( srvr, dn, creds, reqctrls,
+ lderrnop, matcheddnp, errmsgp, refurlsp, resctrlsp );
+ }
+ } while ( PASSTHRU_LDAP_CONN_ERROR( rc ) && --tries > 0 );
+
+ return( rc );
+}
+
+
+/*
+ * like passthru_simple_bind_s() but only makes one attempt.
+ */
+static int
+passthru_simple_bind_once_s( PassThruServer *srvr, char *dn,
+ struct berval *creds, LDAPControl **reqctrls, int *lderrnop,
+ char **matcheddnp, char **errmsgp, struct berval ***refurlsp,
+ LDAPControl ***resctrlsp )
+{
+ int rc, msgid;
+ char **referrals;
+ struct timeval tv, *timeout;
+ LDAPMessage *result;
+ LDAP *ld;
+
+ /*
+ * Grab an LDAP connection to use for this bind.
+ */
+ ld = NULL;
+ if (( rc = passthru_get_connection( srvr, &ld )) != LDAP_SUCCESS ) {
+ goto release_and_return;
+ }
+
+ /*
+ * Send the bind operation (need to retry on LDAP_SERVER_DOWN)
+ */
+ if (( rc = ldap_sasl_bind( ld, dn, LDAP_SASL_SIMPLE, creds, reqctrls,
+ NULL, &msgid )) != LDAP_SUCCESS ) {
+ goto release_and_return;
+ }
+
+ /*
+ * determine timeout value (how long we will wait for a response)
+ * if timeout is NULL or zero'd, we wait indefinitely.
+ */
+ if ( srvr->ptsrvr_timeout == NULL || ( srvr->ptsrvr_timeout->tv_sec == 0
+ && srvr->ptsrvr_timeout->tv_usec == 0 )) {
+ timeout = NULL;
+ } else {
+ tv = *srvr->ptsrvr_timeout; /* struct copy */
+ timeout = &tv;
+ }
+
+ /*
+ * Wait for a result.
+ */
+ rc = ldap_result( ld, msgid, 1, timeout, &result );
+
+ /*
+ * Interpret the result.
+ */
+ if ( rc == 0 ) { /* timeout */
+ /*
+ * Timed out waiting for a reply from the server.
+ */
+ rc = LDAP_TIMEOUT;
+ } else if ( rc < 0 ) {
+ /*
+ * Some other error occurred (no result received).
+ */
+ rc = ldap_get_lderrno( ld, matcheddnp, errmsgp );
+ } else {
+ /*
+ * Got a result from remote server -- parse it.
+ */
+ rc = ldap_parse_result( ld, result, lderrnop, matcheddnp, errmsgp,
+ &referrals, resctrlsp, 1 );
+ if ( referrals != NULL ) {
+ *refurlsp = passthru_strs2bervals( referrals );
+ ldap_value_free( referrals );
+ }
+ }
+
+
+release_and_return:
+ if ( ld != NULL ) {
+ passthru_release_connection( srvr, ld, PASSTHRU_LDAP_CONN_ERROR( rc ));
+ }
+
+ return( rc );
+}
diff --git a/ldap/servers/plugins/passthru/ptconfig.c b/ldap/servers/plugins/passthru/ptconfig.c
new file mode 100644
index 00000000..c3653f66
--- /dev/null
+++ b/ldap/servers/plugins/passthru/ptconfig.c
@@ -0,0 +1,301 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/*
+ * ptconfig.c - configuration-related code for Pass Through Authentication
+ *
+ */
+
+#include "passthru.h"
+
+/*
+ * Configuration is a bit complicated to fit into a single slapd config file
+ * line, but for now that's how it works. The format is:
+ *
+ * plugin preoperation on PTA NSHOME/passthru-plugin.so passthruauth_init ARGS
+ *
+ * where each ARGS provides configuration for one host. Each ARG should
+ * be of the form:
+ *
+ * "ldap://hosts/suffixes maxconns,maxconcurrency,timeout,ldver,connlifetime"
+ * OR
+ * "ldaps://hosts/suffixes maxconns,maxconcurrency,timeout,ldver,connlifetime"
+ *
+ * where:
+ * hosts is a space-separated list of remote servers (with optional port
+ * numbers) to be used. Each one is tried in order when opening an
+ * LDAP connection.
+ * suffixes is a semicolon separated list of DNs (if a DN contains a
+ * semicolon it must be represented \3B),
+ * maxconns is a limit on how many connections will be made,
+ * maxconcurrency is a limit on how many operations can share a connection,
+ * timeout is a time limit in seconds for bind operations to complete (use
+ * 0 to specify an infinite limit).
+ * ldver is the LDAP protocol version to use to talk to the server (2 or 3)
+ * connlifetime is a time limit time in seconds for a connection to be
+ * used before it is closed and reopened (use 0 to specify an infinite
+ * limit). connlifetime can be omitted in which case a default value
+ * is used; this is for compatibility with DS 4.0 which did not support
+ * connlifetime.
+ */
+
+
+/*
+ * function prototypes
+ */
+static char **get_backend_suffixes( void );
+static int is_underneath_backend_suffix( char *normdn, char **besuffixes );
+
+/*
+ * static variables
+ */
+/* for now, there is only one configuration and it is global to the plugin */
+static PassThruConfig theConfig;
+static int inited = 0;
+
+
+/*
+ * Read configuration and create a configuration data structure.
+ * This is called after the server has configured itself so we can check
+ * for things like collisions between our suffixes and backend's suffixes.
+ * Returns an LDAP error code (LDAP_SUCCESS if all goes well).
+ * XXXmcs: this function leaks memory if any errors occur.
+ */
+int
+passthru_config( int argc, char **argv )
+{
+ int i, j, rc, tosecs, using_def_connlifetime;
+ char *p, **suffixarray;
+ PassThruServer *prevsrvr, *srvr;
+ PassThruSuffix *suffix, *prevsuffix;
+ LDAPURLDesc *ludp;
+
+ if ( inited ) {
+ slapi_log_error( SLAPI_LOG_FATAL, PASSTHRU_PLUGIN_SUBSYSTEM,
+ "only one pass through plugin instance can be used\n" );
+ return( LDAP_PARAM_ERROR );
+ }
+
+ inited = 1;
+
+ /*
+ * It doesn't make sense to configure a pass through plugin without
+ * providing at least one remote server. Return an error if attempted.
+ */
+ if ( argc < 1 ) {
+ slapi_log_error( SLAPI_LOG_FATAL, PASSTHRU_PLUGIN_SUBSYSTEM,
+ "no pass through servers found in configuration"
+ " (at least one must be listed)\n" );
+ return( LDAP_PARAM_ERROR );
+ }
+
+ /*
+ * Parse argv[] values.
+ */
+ prevsrvr = NULL;
+ for ( i = 0; i < argc; ++i ) {
+ srvr = (PassThruServer *)slapi_ch_calloc( 1, sizeof( PassThruServer ));
+ srvr->ptsrvr_url = slapi_ch_strdup( argv[i] );
+
+ if (( p = strchr( srvr->ptsrvr_url, ' ' )) == NULL ) {
+ /*
+ * use defaults for maxconnections, maxconcurrency, timeout,
+ * LDAP version, and connlifetime.
+ */
+ srvr->ptsrvr_maxconnections = PASSTHRU_DEF_SRVR_MAXCONNECTIONS;
+ srvr->ptsrvr_maxconcurrency = PASSTHRU_DEF_SRVR_MAXCONCURRENCY;
+ srvr->ptsrvr_timeout = (struct timeval *)slapi_ch_calloc( 1,
+ sizeof( struct timeval ));
+ srvr->ptsrvr_timeout->tv_sec = PASSTHRU_DEF_SRVR_TIMEOUT;
+ srvr->ptsrvr_ldapversion = PASSTHRU_DEF_SRVR_PROTOCOL_VERSION;
+ using_def_connlifetime = 1;
+ } else {
+ /*
+ * parse parameters. format is:
+ * maxconnections,maxconcurrency,timeout,ldapversion
+ * OR maxconnections,maxconcurrency,timeout,ldapversion,lifetime
+ */
+ *p++ = '\0';
+ rc = sscanf( p, "%d,%d,%d,%d,%d", &srvr->ptsrvr_maxconnections,
+ &srvr->ptsrvr_maxconcurrency, &tosecs,
+ &srvr->ptsrvr_ldapversion, &srvr->ptsrvr_connlifetime );
+ if ( rc < 4 ) {
+ slapi_log_error( SLAPI_LOG_FATAL, PASSTHRU_PLUGIN_SUBSYSTEM,
+ "server parameters should be in the form "
+ "\"maxconnections,maxconcurrency,timeout,ldapversion,"
+ "connlifetime\" (got \"%s\")\n", p );
+ return( LDAP_PARAM_ERROR );
+ } else if ( rc < 5 ) {
+ using_def_connlifetime = 1;
+ srvr->ptsrvr_connlifetime = PASSTHRU_DEF_SRVR_CONNLIFETIME;
+ } else {
+ using_def_connlifetime = 0;
+ }
+
+ if ( srvr->ptsrvr_ldapversion != LDAP_VERSION2
+ && srvr->ptsrvr_ldapversion != LDAP_VERSION3 ) {
+ slapi_log_error( SLAPI_LOG_FATAL, PASSTHRU_PLUGIN_SUBSYSTEM,
+ "LDAP protocol version should be %d or %d (got %d)\n",
+ LDAP_VERSION2, LDAP_VERSION3,
+ srvr->ptsrvr_ldapversion );
+ return( LDAP_PARAM_ERROR );
+ }
+
+ if ( srvr->ptsrvr_maxconnections <= 0 ) {
+ slapi_log_error( SLAPI_LOG_FATAL, PASSTHRU_PLUGIN_SUBSYSTEM,
+ "maximum connections must be greater than "
+ "zero (got %d)\n", srvr->ptsrvr_maxconnections );
+ return( LDAP_PARAM_ERROR );
+ }
+
+ if ( srvr->ptsrvr_maxconcurrency <= 0 ) {
+ slapi_log_error( SLAPI_LOG_FATAL, PASSTHRU_PLUGIN_SUBSYSTEM,
+ "maximum concurrency must be greater than "
+ "zero (got %d)\n", srvr->ptsrvr_maxconcurrency );
+ return( LDAP_PARAM_ERROR );
+ }
+
+ if ( tosecs <= 0 ) {
+ srvr->ptsrvr_timeout = NULL;
+ } else {
+ srvr->ptsrvr_timeout = (struct timeval *)slapi_ch_calloc( 1,
+ sizeof( struct timeval ));
+ srvr->ptsrvr_timeout->tv_sec = tosecs;
+ }
+ }
+
+ /*
+ * parse the LDAP URL
+ */
+ if (( rc = ldap_url_parse( srvr->ptsrvr_url, &ludp )) != 0 ) {
+ slapi_log_error( SLAPI_LOG_FATAL, PASSTHRU_PLUGIN_SUBSYSTEM,
+ "unable to parse LDAP URL \"%s\" (%s)\n",
+ srvr->ptsrvr_url, passthru_urlparse_err2string( rc ));
+ return( LDAP_PARAM_ERROR );
+ }
+
+ if ( ludp->lud_dn == NULL || *ludp->lud_dn == '\0' ) {
+ slapi_log_error( SLAPI_LOG_FATAL, PASSTHRU_PLUGIN_SUBSYSTEM,
+ "missing suffix in LDAP URL \"%s\"\n",
+ srvr->ptsrvr_url );
+ return( LDAP_PARAM_ERROR );
+ }
+
+ srvr->ptsrvr_hostname = slapi_ch_strdup( ludp->lud_host );
+ srvr->ptsrvr_port = ludp->lud_port;
+ srvr->ptsrvr_secure =
+ (( ludp->lud_options & LDAP_URL_OPT_SECURE ) != 0 );
+
+ /*
+ * If a space-separated list of hosts is configured for failover,
+ * use a different (non infinite) default for connection lifetime.
+ */
+ if ( using_def_connlifetime &&
+ strchr( srvr->ptsrvr_hostname, ' ' ) != NULL ) {
+ srvr->ptsrvr_connlifetime =
+ PASSTHRU_DEF_SRVR_FAILOVERCONNLIFETIME;
+ }
+
+ /*
+ * split the DN into multiple suffixes (separated by ';')
+ */
+ if (( suffixarray = ldap_str2charray( ludp->lud_dn, ";" )) == NULL ) {
+ slapi_log_error( SLAPI_LOG_FATAL, PASSTHRU_PLUGIN_SUBSYSTEM,
+ "unable to parse suffix string \"%s\" within \"%s\"\n",
+ ludp->lud_dn, srvr->ptsrvr_url );
+ return( LDAP_PARAM_ERROR );
+ }
+
+ /*
+ * free our LDAP URL descriptor
+ */
+ ldap_free_urldesc( ludp );
+ ludp = NULL;
+
+ /*
+ * reorganize the suffixes into a linked list and normalize them
+ */
+ prevsuffix = NULL;
+ for ( j = 0; suffixarray[ j ] != NULL; ++j ) {
+
+ /*
+ * allocate a new PassThruSuffix structure and fill it in.
+ */
+ suffix = (PassThruSuffix *)slapi_ch_malloc(
+ sizeof( PassThruSuffix ));
+ suffix->ptsuffix_normsuffix =
+ slapi_dn_normalize( suffixarray[ j ] );
+ suffixarray[ j ] = NULL;
+ suffix->ptsuffix_len = strlen( suffix->ptsuffix_normsuffix );
+ suffix->ptsuffix_next = NULL;
+
+ /*
+ * add to end of list
+ */
+ if ( prevsuffix == NULL ) {
+ srvr->ptsrvr_suffixes = suffix;
+ } else {
+ prevsuffix->ptsuffix_next = suffix;
+ }
+ prevsuffix = suffix;
+ }
+ ldap_memfree( suffixarray );
+
+ /*
+ * create mutexes and condition variables for this server
+ */
+ if (( srvr->ptsrvr_connlist_mutex = slapi_new_mutex()) == NULL ||
+ ( srvr->ptsrvr_connlist_cv = slapi_new_condvar(
+ srvr->ptsrvr_connlist_mutex )) == NULL ) {
+ return( LDAP_LOCAL_ERROR );
+ }
+
+ /*
+ * add this server to the end of our list
+ */
+ if ( prevsrvr == NULL ) {
+ theConfig.ptconfig_serverlist = srvr;
+ } else {
+ prevsrvr->ptsrvr_next = srvr;
+ }
+ prevsrvr = srvr;
+
+#ifdef PASSTHRU_VERBOSE_LOGGING
+ /*
+ * log configuration for debugging purposes
+ */
+ slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM,
+ "PTA server host: \"%s\", port: %d, secure: %d,"
+ " maxconnections: %d, maxconcurrency: %d, timeout: %d,"
+ " ldversion: %d, connlifetime: %d\n",
+ srvr->ptsrvr_hostname, srvr->ptsrvr_port,
+ srvr->ptsrvr_secure, srvr->ptsrvr_maxconnections,
+ srvr->ptsrvr_maxconcurrency,
+ srvr->ptsrvr_timeout == NULL ? -1
+ : srvr->ptsrvr_timeout->tv_sec, srvr->ptsrvr_ldapversion,
+ srvr->ptsrvr_connlifetime );
+ for ( prevsuffix = srvr->ptsrvr_suffixes; prevsuffix != NULL;
+ prevsuffix = prevsuffix->ptsuffix_next ) {
+ slapi_log_error( SLAPI_LOG_FATAL, PASSTHRU_PLUGIN_SUBSYSTEM,
+ " normalized suffix: \"%s\"\n",
+ prevsuffix->ptsuffix_normsuffix );
+ }
+#endif
+
+ }
+
+ return( LDAP_SUCCESS );
+}
+
+
+/*
+ * Get the pass though configuration data. For now, there is only one
+ * configuration and it is global to the plugin.
+ */
+PassThruConfig *
+passthru_get_config( void )
+{
+ return( &theConfig );
+}
diff --git a/ldap/servers/plugins/passthru/ptconn.c b/ldap/servers/plugins/passthru/ptconn.c
new file mode 100644
index 00000000..56e2e0cc
--- /dev/null
+++ b/ldap/servers/plugins/passthru/ptconn.c
@@ -0,0 +1,420 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/*
+ * ptconn.c - LDAP connection-related code for Pass Through Authentication
+ *
+ */
+
+#include "passthru.h"
+
+/*
+ * function prototypes
+ */
+static int dn_is_underneath_suffix( PassThruSuffix *suffix, char *normdn,
+ int dnlen );
+static void close_and_dispose_connection( PassThruConnection *conn );
+static void check_for_stale_connections( PassThruServer *srvr );
+
+
+/*
+ * Most of the complicated connection-related code lives in this file. Some
+ * general notes about how we manage our connections to "remote" LDAP servers:
+ *
+ * 1) Each server we have a relationship with is managed independently.
+ *
+ * 2) We may simultaneously issue multiple bind requests on a single LDAP
+ * connection. Each server has a "maxconcurrency" configuration
+ * parameter associated with it that caps the number of outstanding
+ * binds per connection. For each connection we maintain a "usecount"
+ * which is used to track the number of threads using the connection.
+ *
+ * 3) We may open more than one connection to a server. This is only done
+ * when "maxconcurrency" is exceeded for all the connections we already
+ * have open. Each server has a "maxconnections" configuration
+ * parameter associated with it that caps the number of connections.
+ * We also maintain a "connlist_count" for each server so we know when
+ * we have reached the maximum number of open connections allowed.
+ *
+ * 4) If no connection is available to service a request (and we have
+ * reached the limit of how many we are supposed to open), threads
+ * go to sleep on a condition variable and one is woken up each time
+ * a connection's "usecount" is decremented.
+ *
+ * 5) If we see an LDAP_CONNECT_ERROR or LDAP_SERVER_DOWN error on a
+ * session handle, we mark its status as PASSTHRU_CONNSTATUS_DOWN and
+ * close it as soon as all threads using it release it. Connections
+ * marked as "down" are not counted against the "maxconnections" limit.
+ *
+ * 6) We close and reopen connections that have been open for more than
+ * the server's configured connection lifetime. This is done to ensure
+ * that we reconnect to a primary server after failover occurs. If no
+ * lifetime is configured or it is set to 0, we never close and reopen
+ * connections.
+ */
+
+
+/*
+ * Given a normalized target dn, see if it we should "pass through"
+ * authentication to another LDAP server. The answer is "yes" if the
+ * target dn resides under one of the suffixes we have that is associated
+ * with an LDAP server we know about.
+ *
+ * This function assumes that normdn is normalized and the the suffixes in the
+ * cfg structure have also been normalized.
+ *
+ * Returns an LDAP error code, typically:
+ * LDAP_SUCCESS should pass though; *srvrp set.
+ * LDAP_NO_SUCH_OBJECT let this server handle the bind.
+ */
+int
+passthru_dn2server( PassThruConfig *cfg, char *normdn, PassThruServer **srvrp )
+{
+ PassThruServer *ptsrvr;
+ PassThruSuffix *ptsuffix;
+ int dnlen;
+
+ PASSTHRU_ASSERT( cfg != NULL );
+ PASSTHRU_ASSERT( normdn != NULL );
+ PASSTHRU_ASSERT( srvrp != NULL );
+
+ dnlen = strlen( normdn );
+
+ for ( ptsrvr = cfg->ptconfig_serverlist; ptsrvr != NULL;
+ ptsrvr = ptsrvr->ptsrvr_next ) {
+ for ( ptsuffix = ptsrvr->ptsrvr_suffixes; ptsuffix != NULL;
+ ptsuffix = ptsuffix->ptsuffix_next ) {
+ if ( dn_is_underneath_suffix( ptsuffix, normdn, dnlen )) {
+ *srvrp = ptsrvr;
+ return( LDAP_SUCCESS ); /* got it */
+ }
+ }
+ }
+
+ *srvrp = NULL;
+ return( LDAP_NO_SUCH_OBJECT ); /* no match */
+}
+
+
+/*
+ * Get an LDAP session handle for communicating with srvr.
+ *
+ * Returns an LDAP eror code, typically:
+ * LDAP_SUCCESS
+ * other
+ */
+int
+passthru_get_connection( PassThruServer *srvr, LDAP **ldp )
+{
+ int rc;
+ PassThruConnection *conn, *connprev;
+ LDAP *ld;
+
+ PASSTHRU_ASSERT( srvr != NULL );
+ PASSTHRU_ASSERT( ldp != NULL );
+
+ check_for_stale_connections( srvr );
+
+ slapi_lock_mutex( srvr->ptsrvr_connlist_mutex );
+ rc = LDAP_SUCCESS; /* optimistic */
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM,
+ "=> passthru_get_connection server %s:%d conns: %d maxconns: %d\n",
+ srvr->ptsrvr_hostname, srvr->ptsrvr_port, srvr->ptsrvr_connlist_count,
+ srvr->ptsrvr_maxconnections );
+
+ for ( ;; ) {
+ /*
+ * look for an available, already open connection
+ */
+ connprev = NULL;
+ for ( conn = srvr->ptsrvr_connlist; conn != NULL;
+ conn = conn->ptconn_next ) {
+ if ( conn->ptconn_status == PASSTHRU_CONNSTATUS_OK
+ && conn->ptconn_usecount < srvr->ptsrvr_maxconcurrency ) {
+#ifdef PASSTHRU_VERBOSE_LOGGING
+ slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM,
+ "<= passthru_get_connection server found "
+ "conn 0x%x to use)\n", conn->ptconn_ld );
+#endif
+ goto unlock_and_return; /* found one */
+ }
+ connprev = conn;
+ }
+
+ if ( srvr->ptsrvr_connlist_count < srvr->ptsrvr_maxconnections ) {
+ /*
+ * we have not exceeded the maximum number of connections allowed,
+ * so we initialize a new one and add it to the end of our list.
+ */
+ if (( ld = slapi_ldap_init( srvr->ptsrvr_hostname,
+ srvr->ptsrvr_port, srvr->ptsrvr_secure, 1 )) == NULL ) {
+#ifdef PASSTHRU_VERBOSE_LOGGING
+ slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM,
+ "<= passthru_get_connection slapi_ldap_init failed\n" );
+#endif
+ rc = LDAP_LOCAL_ERROR;
+ goto unlock_and_return;
+ }
+
+ /*
+ * set protocol version to correct value for this server
+ */
+ if ( ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION,
+ &srvr->ptsrvr_ldapversion ) != 0 ) {
+ slapi_ldap_unbind( ld );
+ }
+
+ conn = (PassThruConnection *)slapi_ch_malloc(
+ sizeof( PassThruConnection ));
+ conn->ptconn_ld = ld;
+ conn->ptconn_status = PASSTHRU_CONNSTATUS_OK;
+ time( &conn->ptconn_opentime );
+ conn->ptconn_ldapversion = srvr->ptsrvr_ldapversion;
+ conn->ptconn_usecount = 0;
+ conn->ptconn_next = NULL;
+ conn->ptconn_prev = connprev;
+ if ( connprev == NULL ) {
+ srvr->ptsrvr_connlist = conn;
+ conn->ptconn_prev = NULL;
+ } else {
+ connprev->ptconn_next = conn;
+ conn->ptconn_prev = connprev;
+ }
+
+ ++srvr->ptsrvr_connlist_count;
+
+#ifdef PASSTHRU_VERBOSE_LOGGING
+ slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM,
+ "<= passthru_get_connection added new conn 0x%x, "
+ "conn count now %d\n", ld, srvr->ptsrvr_connlist_count );
+#endif
+ goto unlock_and_return; /* got a new one */
+ }
+
+#ifdef PASSTHRU_VERBOSE_LOGGING
+ slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM,
+ "... passthru_get_connection waiting for conn to free up\n" );
+#endif
+ slapi_wait_condvar( srvr->ptsrvr_connlist_cv, NULL );
+
+#ifdef PASSTHRU_VERBOSE_LOGGING
+ slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM,
+ "... passthru_get_connection awake again\n" );
+#endif
+ }
+
+unlock_and_return:
+ if ( conn != NULL ) {
+ ++conn->ptconn_usecount;
+ *ldp = conn->ptconn_ld;
+ slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM,
+ "<= passthru_get_connection ld=0x%x (concurrency now %d)\n",
+ *ldp, conn->ptconn_usecount );
+ } else {
+ slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM,
+ "<= passthru_get_connection error %d\n", rc );
+ }
+
+ slapi_unlock_mutex( srvr->ptsrvr_connlist_mutex );
+ return( rc );
+}
+
+
+/*
+ * Mark the connection ld is associated with as free to be used again.
+ * If dispose is non-zero, we mark the connection as "bad" and dispose
+ * of it and its ld once the use count becomes zero.
+ */
+void
+passthru_release_connection( PassThruServer *srvr, LDAP *ld, int dispose )
+{
+ PassThruConnection *conn, *connprev;
+
+ PASSTHRU_ASSERT( srvr != NULL );
+ PASSTHRU_ASSERT( ld != NULL );
+
+#ifdef PASSTHRU_VERBOSE_LOGGING
+ slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM,
+ "=> passthru_release_connection ld=0x%x%s\n", ld,
+ dispose ? " (disposing)" : "" );
+#endif
+
+ slapi_lock_mutex( srvr->ptsrvr_connlist_mutex );
+
+ /*
+ * find the connection structure this ld is part of
+ */
+ connprev = NULL;
+ for ( conn = srvr->ptsrvr_connlist; conn != NULL;
+ conn = conn->ptconn_next ) {
+ if ( ld == conn->ptconn_ld ) {
+ break;
+ }
+ connprev = conn;
+ }
+
+ if ( conn == NULL ) { /* ld not found -- unexpected */
+ slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM,
+ "=> passthru_release_connection ld=0x%x not found\n", ld );
+ } else {
+ PASSTHRU_ASSERT( conn->ptconn_usecount > 0 );
+ --conn->ptconn_usecount;
+ if ( dispose ) {
+ conn->ptconn_status = PASSTHRU_CONNSTATUS_DOWN;
+ }
+
+ if ( conn->ptconn_status != PASSTHRU_CONNSTATUS_OK
+ && conn->ptconn_usecount == 0 ) {
+ /*
+ * remove from server's connection list
+ */
+ if ( connprev == NULL ) {
+ srvr->ptsrvr_connlist = conn->ptconn_next;
+ } else {
+ connprev->ptconn_next = conn->ptconn_next;
+ }
+ --srvr->ptsrvr_connlist_count;
+
+ /*
+ * close connection and free memory
+ */
+ close_and_dispose_connection( conn );
+ }
+ }
+
+ /*
+ * wake up a thread that is waiting for a connection (there may not be
+ * any but the slapi_notify_condvar() call should be cheap in any event).
+ */
+ slapi_notify_condvar( srvr->ptsrvr_connlist_cv, 0 );
+
+ /*
+ * unlock and return
+ */
+ slapi_unlock_mutex( srvr->ptsrvr_connlist_mutex );
+}
+
+
+/*
+ * close all open connections in preparation for server shutdown, etc.
+ */
+void
+passthru_close_all_connections( PassThruConfig *cfg )
+{
+ PassThruServer *srvr;
+ PassThruConnection *conn, *nextconn;
+
+ PASSTHRU_ASSERT( cfg != NULL );
+
+ for ( srvr = cfg->ptconfig_serverlist; srvr != NULL;
+ srvr = srvr->ptsrvr_next ) {
+ for ( conn = srvr->ptsrvr_connlist; conn != NULL; conn = nextconn ) {
+ nextconn = conn->ptconn_next;
+ close_and_dispose_connection( conn );
+ }
+ }
+}
+
+
+/*
+ * return non-zero value if normdn falls underneath a suffix
+ */
+static int
+dn_is_underneath_suffix( PassThruSuffix *suffix, char *normdn, int dnlen )
+{
+ PASSTHRU_ASSERT( suffix != NULL );
+ PASSTHRU_ASSERT( normdn != NULL );
+ PASSTHRU_ASSERT( dnlen >= 0 );
+
+ return ( suffix->ptsuffix_len <= dnlen &&
+ slapi_UTF8CASECMP( suffix->ptsuffix_normsuffix,
+ normdn + ( dnlen - suffix->ptsuffix_len )) == 0 );
+}
+
+
+/*
+ * Unbind from server and dispose of a connection.
+ */
+static void
+close_and_dispose_connection( PassThruConnection *conn )
+{
+ PASSTHRU_ASSERT( conn != NULL );
+ PASSTHRU_ASSERT( conn->ptconn_ld != NULL );
+
+ slapi_ldap_unbind( conn->ptconn_ld );
+ conn->ptconn_ld = NULL;
+ slapi_ch_free( (void **)&conn );
+}
+
+
+/*
+ * Close (or mark to be closed) any connections for this srvr that have
+ * exceeded the maximum connection lifetime.
+ */
+static void
+check_for_stale_connections( PassThruServer *srvr )
+{
+ PassThruConnection *conn, *prevconn, *nextconn;
+ time_t curtime;
+
+ PASSTHRU_ASSERT( srvr != NULL );
+
+#ifdef PASSTHRU_VERBOSE_LOGGING
+ slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM,
+ "check_for_stale_connections: server %s (lifetime %d secs)\n",
+ srvr->ptsrvr_url, srvr->ptsrvr_connlifetime );
+#endif
+
+
+ if ( srvr->ptsrvr_connlifetime <= 0 ) {
+ return;
+ }
+
+ time( &curtime );
+
+ slapi_lock_mutex( srvr->ptsrvr_connlist_mutex );
+
+ prevconn = NULL;
+ for ( conn = srvr->ptsrvr_connlist; conn != NULL; conn = nextconn ) {
+ nextconn = conn->ptconn_next;
+
+ if ( curtime - conn->ptconn_opentime > srvr->ptsrvr_connlifetime ) {
+ if ( conn->ptconn_usecount == 0 ) {
+ /*
+ * connection is idle and stale -- remove from server's list
+ */
+#ifdef PASSTHRU_VERBOSE_LOGGING
+ slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM,
+ "check_for_stale_connections: discarding idle, "
+ "stale connection 0x%x\n", conn->ptconn_ld );
+#endif
+ if ( prevconn == NULL ) {
+ srvr->ptsrvr_connlist = nextconn;
+ } else {
+ prevconn->ptconn_next = nextconn;
+ }
+ --srvr->ptsrvr_connlist_count;
+ close_and_dispose_connection( conn );
+ } else {
+ /*
+ * connection is stale but in use -- mark to be disposed later
+ */
+#ifdef PASSTHRU_VERBOSE_LOGGING
+ slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM,
+ "check_for_stale_connections: marking connection 0x%x "
+ "stale (use count %d)\n", conn->ptconn_ld,
+ conn->ptconn_usecount );
+#endif
+ conn->ptconn_status = PASSTHRU_CONNSTATUS_STALE;
+ prevconn = conn;
+ }
+ } else {
+ prevconn = conn;
+ }
+ }
+
+ slapi_unlock_mutex( srvr->ptsrvr_connlist_mutex );
+}
diff --git a/ldap/servers/plugins/passthru/ptdebug.c b/ldap/servers/plugins/passthru/ptdebug.c
new file mode 100644
index 00000000..0f01c4a7
--- /dev/null
+++ b/ldap/servers/plugins/passthru/ptdebug.c
@@ -0,0 +1,23 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/*
+ * ptdebug.c - debugging-related code for Pass Through Authentication
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include "passthru.h"
+
+#ifdef _WIN32
+int *module_ldap_debug = 0;
+
+void plugin_init_debug_level(int *level_ptr)
+{
+ module_ldap_debug = level_ptr;
+}
+#endif
diff --git a/ldap/servers/plugins/passthru/ptdllmain.c b/ldap/servers/plugins/passthru/ptdllmain.c
new file mode 100644
index 00000000..0e9eaccf
--- /dev/null
+++ b/ldap/servers/plugins/passthru/ptdllmain.c
@@ -0,0 +1,131 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include "ldap.h"
+#include "lber.h"
+#include "passthru.h"
+
+
+#ifdef _WIN32
+/* Lifted from Q125688
+ * How to Port a 16-bit DLL to a Win32 DLL
+ * on the MSVC 4.0 CD
+ */
+BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved)
+{
+ WSADATA wsadata;
+
+ switch (fdwReason)
+ {
+ case DLL_PROCESS_ATTACH:
+ /* Code from LibMain inserted here. Return TRUE to keep the
+ DLL loaded or return FALSE to fail loading the DLL.
+
+ You may have to modify the code in your original LibMain to
+ account for the fact that it may be called more than once.
+ You will get one DLL_PROCESS_ATTACH for each process that
+ loads the DLL. This is different from LibMain which gets
+ called only once when the DLL is loaded. The only time this
+ is critical is when you are using shared data sections.
+ If you are using shared data sections for statically
+ allocated data, you will need to be careful to initialize it
+ only once. Check your code carefully.
+
+ Certain one-time initializations may now need to be done for
+ each process that attaches. You may also not need code from
+ your original LibMain because the operating system may now
+ be doing it for you.
+ */
+ /*
+ * 16 bit code calls UnlockData()
+ * which is mapped to UnlockSegment in windows.h
+ * in 32 bit world UnlockData is not defined anywhere
+ * UnlockSegment is mapped to GlobalUnfix in winbase.h
+ * and the docs for both UnlockSegment and GlobalUnfix say
+ * ".. function is oboslete. Segments have no meaning
+ * in the 32-bit environment". So we do nothing here.
+ */
+
+ if( errno = WSAStartup(0x0101, &wsadata ) != 0 )
+ return FALSE;
+
+ break;
+
+ case DLL_THREAD_ATTACH:
+ /* Called each time a thread is created in a process that has
+ already loaded (attached to) this DLL. Does not get called
+ for each thread that exists in the process before it loaded
+ the DLL.
+
+ Do thread-specific initialization here.
+ */
+ break;
+
+ case DLL_THREAD_DETACH:
+ /* Same as above, but called when a thread in the process
+ exits.
+
+ Do thread-specific cleanup here.
+ */
+ break;
+
+ case DLL_PROCESS_DETACH:
+ /* Code from _WEP inserted here. This code may (like the
+ LibMain) not be necessary. Check to make certain that the
+ operating system is not doing it for you.
+ */
+ WSACleanup();
+
+ break;
+ }
+ /* The return value is only used for DLL_PROCESS_ATTACH; all other
+ conditions are ignored. */
+ return TRUE; // successful DLL_PROCESS_ATTACH
+}
+#else
+int CALLBACK
+LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine )
+{
+ /*UnlockData( 0 );*/
+ return( 1 );
+}
+#endif
+
+#ifdef LDAP_DEBUG
+#ifndef _WIN32
+#include <stdarg.h>
+#include <stdio.h>
+
+void LDAPDebug( int level, char* fmt, ... )
+{
+ static char debugBuf[1024];
+
+ if (module_ldap_debug && (*module_ldap_debug & level))
+ {
+ va_list ap;
+ va_start (ap, fmt);
+ _snprintf (debugBuf, sizeof(debugBuf), fmt, ap);
+ va_end (ap);
+
+ OutputDebugString (debugBuf);
+ }
+}
+#endif
+#endif
+
+#ifndef _WIN32
+
+/* The 16-bit version of the RTL does not implement perror() */
+
+#include <stdio.h>
+
+void perror( const char *msg )
+{
+ char buf[128];
+ wsprintf( buf, "%s: error %d\n", msg, WSAGetLastError()) ;
+ OutputDebugString( buf );
+}
+
+#endif
diff --git a/ldap/servers/plugins/passthru/ptpreop.c b/ldap/servers/plugins/passthru/ptpreop.c
new file mode 100644
index 00000000..aaad0621
--- /dev/null
+++ b/ldap/servers/plugins/passthru/ptpreop.c
@@ -0,0 +1,252 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/*
+ * ptpreop.c - bind pre-operation plugin for Pass Through Authentication
+ *
+ */
+
+#include "passthru.h"
+
+static Slapi_PluginDesc pdesc = { "passthruauth", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT,
+ "pass through authentication plugin" };
+
+/*
+ * function prototypes
+ */
+static int passthru_bindpreop( Slapi_PBlock *pb );
+static int passthru_bindpreop_start( Slapi_PBlock *pb );
+static int passthru_bindpreop_close( Slapi_PBlock *pb );
+
+
+/*
+ * Plugin initialization function (which must be listed in the appropriate
+ * slapd config file).
+ */
+int
+passthruauth_init( Slapi_PBlock *pb )
+{
+ PASSTHRU_ASSERT( pb != NULL );
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM,
+ "=> passthruauth_init\n" );
+
+ if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION,
+ (void *)SLAPI_PLUGIN_VERSION_01 ) != 0
+ || slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION,
+ (void *)&pdesc ) != 0
+ || slapi_pblock_set( pb, SLAPI_PLUGIN_START_FN,
+ (void *)passthru_bindpreop_start ) != 0
+ || slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_BIND_FN,
+ (void *)passthru_bindpreop ) != 0
+ || slapi_pblock_set( pb, SLAPI_PLUGIN_CLOSE_FN,
+ (void *)passthru_bindpreop_close ) != 0 ) {
+ slapi_log_error( SLAPI_LOG_FATAL, PASSTHRU_PLUGIN_SUBSYSTEM,
+ "passthruauth_init failed\n" );
+ return( -1 );
+ }
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM,
+ "<= passthruauth_init succeeded\n" );
+
+ return( 0 );
+}
+
+
+/*
+ * passthru_bindpreop_start() is called before the directory server
+ * is fully up. We parse our configuration and initialize any mutexes, etc.
+ */
+static int
+passthru_bindpreop_start( Slapi_PBlock *pb )
+{
+ int argc, rc;
+ char **argv;
+
+ PASSTHRU_ASSERT( pb != NULL );
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM,
+ "=> passthru_bindpreop_start\n" );
+
+ if ( slapi_pblock_get( pb, SLAPI_PLUGIN_ARGC, &argc ) != 0 ||
+ slapi_pblock_get( pb, SLAPI_PLUGIN_ARGV, &argv ) != 0 ) {
+ slapi_log_error( SLAPI_LOG_FATAL, PASSTHRU_PLUGIN_SUBSYSTEM,
+ "unable to get arguments\n" );
+ return( -1 );
+ }
+
+ if (( rc = passthru_config( argc, argv )) != LDAP_SUCCESS ) {
+ slapi_log_error( SLAPI_LOG_FATAL, PASSTHRU_PLUGIN_SUBSYSTEM,
+ "configuration failed (%s)\n", ldap_err2string( rc ));
+ return( -1 );
+ }
+
+ return( 0 );
+}
+
+
+/*
+ * Called right before the Directory Server shuts down.
+ */
+static int
+passthru_bindpreop_close( Slapi_PBlock *pb )
+{
+ PASSTHRU_ASSERT( pb != NULL );
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM,
+ "=> passthru_bindpreop_close\n" );
+
+ /*
+ * close all our open connections.
+ * XXXmcs: free any memory, mutexes, etc.
+ */
+ passthru_close_all_connections( passthru_get_config() );
+
+ return( 0 );
+}
+
+
+static int
+passthru_bindpreop( Slapi_PBlock *pb )
+{
+ int rc, method;
+ char *normbinddn, *matcheddn;
+ char *libldap_errmsg, *pr_errmsg, *errmsg;
+ PassThruConfig *cfg;
+ PassThruServer *srvr;
+ struct berval *creds, **urls;
+ LDAPControl **reqctrls, **resctrls;
+
+ PASSTHRU_ASSERT( pb != NULL );
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM,
+ "=> passthru_bindpreop\n" );
+
+ /*
+ * retrieve parameters for bind operation
+ */
+ if ( slapi_pblock_get( pb, SLAPI_BIND_METHOD, &method ) != 0 ||
+ slapi_pblock_get( pb, SLAPI_BIND_TARGET, &normbinddn ) != 0 ||
+ slapi_pblock_get( pb, SLAPI_BIND_CREDENTIALS, &creds ) != 0 ) {
+ slapi_log_error( SLAPI_LOG_FATAL, PASSTHRU_PLUGIN_SUBSYSTEM,
+ "<= not handled (unable to retrieve bind parameters)\n" );
+ return( PASSTHRU_OP_NOT_HANDLED );
+ }
+ if ( normbinddn == NULL ) {
+ normbinddn = "";
+ }
+
+ /*
+ * We only handle simple bind requests that include non-NULL binddn and
+ * credentials. Let the Directory Server itself handle everything else.
+ */
+ if ( method != LDAP_AUTH_SIMPLE || *normbinddn == '\0'
+ || creds->bv_len == 0 ) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM,
+ "<= not handled (not simple bind or NULL dn/credentials)\n" );
+ return( PASSTHRU_OP_NOT_HANDLED );
+ }
+
+ /*
+ * Get pass through authentication configuration.
+ */
+ cfg = passthru_get_config();
+
+ /*
+ * Check to see if the target DN is one we should "pass through" to
+ * another server.
+ */
+ if ( passthru_dn2server( cfg, normbinddn, &srvr ) != LDAP_SUCCESS ) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM,
+ "<= not handled (not one of our suffixes)\n" );
+ return( PASSTHRU_OP_NOT_HANDLED );
+ }
+
+ /*
+ * We are now committed to handling this bind request.
+ * Chain it off to another server.
+ */
+ matcheddn = errmsg = libldap_errmsg = pr_errmsg = NULL;
+ urls = NULL;
+ resctrls = NULL;
+ if ( slapi_pblock_get( pb, SLAPI_REQCONTROLS, &reqctrls ) != 0 ) {
+ rc = LDAP_OPERATIONS_ERROR;
+ errmsg = "unable to retrieve bind controls";
+ slapi_log_error( SLAPI_LOG_FATAL, PASSTHRU_PLUGIN_SUBSYSTEM, "%s\n",
+ errmsg );
+ } else {
+ int lderrno;
+
+ if (( rc = passthru_simple_bind_s( pb, srvr, PASSTHRU_CONN_TRIES,
+ normbinddn, creds, reqctrls, &lderrno, &matcheddn,
+ &libldap_errmsg, &urls, &resctrls )) == LDAP_SUCCESS ) {
+ rc = lderrno;
+ errmsg = libldap_errmsg;
+ } else if ( rc != LDAP_USER_CANCELLED ) { /* not abandoned */
+ PRErrorCode prerr = PR_GetError();
+ pr_errmsg = PR_smprintf( "error %d - %s %s ("
+ SLAPI_COMPONENT_NAME_NSPR " error %d - %s)",
+ rc, ldap_err2string( rc ), srvr->ptsrvr_url,
+ prerr, slapd_pr_strerror(prerr));
+ if ( NULL != pr_errmsg ) {
+ errmsg = pr_errmsg;
+ } else {
+ errmsg = ldap_err2string( rc );
+ }
+ rc = LDAP_OPERATIONS_ERROR;
+ }
+ }
+
+ /*
+ * If bind succeeded, change authentication information associated
+ * with this connection.
+ */
+ if ( rc == LDAP_SUCCESS ) {
+ char *ndn = slapi_ch_strdup( normbinddn );
+ if (slapi_pblock_set(pb, SLAPI_CONN_DN, ndn) != 0 ||
+ slapi_pblock_set(pb, SLAPI_CONN_AUTHMETHOD,
+ SLAPD_AUTH_SIMPLE) != 0) {
+ slapi_ch_free((void **)&ndn);
+ rc = LDAP_OPERATIONS_ERROR;
+ errmsg = "unable to set connection DN or AUTHTYPE";
+ slapi_log_error( SLAPI_LOG_FATAL, PASSTHRU_PLUGIN_SUBSYSTEM,
+ "%s\n", errmsg );
+ }
+ }
+
+ if ( rc != LDAP_USER_CANCELLED ) { /* not abandoned */
+ /*
+ * Send a result to our client.
+ */
+ if ( resctrls != NULL ) {
+ (void)slapi_pblock_set( pb, SLAPI_RESCONTROLS, &resctrls );
+ }
+ slapi_send_ldap_result( pb, rc, matcheddn, errmsg, 0, urls );
+ }
+
+ /*
+ * Clean up -- free allocated memory, etc.
+ */
+ if ( urls != NULL ) {
+ passthru_free_bervals( urls );
+ }
+ if ( libldap_errmsg != NULL ) {
+ ldap_memfree( errmsg );
+ }
+ if ( pr_errmsg != NULL ) {
+ PR_smprintf_free( pr_errmsg );
+ }
+ if ( resctrls != NULL ) {
+ ldap_controls_free( resctrls );
+ }
+ if ( matcheddn != NULL ) {
+ ldap_memfree( matcheddn );
+ }
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, PASSTHRU_PLUGIN_SUBSYSTEM,
+ "<= handled (error %d - %s)\n", rc, ldap_err2string( rc ));
+
+ return( PASSTHRU_OP_HANDLED );
+}
diff --git a/ldap/servers/plugins/passthru/ptutil.c b/ldap/servers/plugins/passthru/ptutil.c
new file mode 100644
index 00000000..4a1b307b
--- /dev/null
+++ b/ldap/servers/plugins/passthru/ptutil.c
@@ -0,0 +1,111 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/*
+ * ptutil.c - utility functions for Pass Through Authentication
+ *
+ */
+
+#include "passthru.h"
+
+
+/*
+ * Convert a char * array into a struct berval * array.
+ * Always succeeds.
+ */
+struct berval **
+passthru_strs2bervals( char **ss )
+{
+ int i;
+ struct berval **bvs;
+
+ if ( ss == NULL || ss[0] == NULL ) {
+ return( NULL );
+ }
+
+ for ( i = 0; ss[i] != NULL; ++i ) {
+ ;
+ }
+
+ bvs = (struct berval **)slapi_ch_calloc( i + 1, sizeof( struct berval * ));
+ for ( i = 0; ss[i] != NULL; ++i ) {
+ bvs[i] = (struct berval *)slapi_ch_malloc( sizeof( struct berval ));
+ bvs[i]->bv_val = slapi_ch_strdup( ss[i] );
+ bvs[i]->bv_len = strlen( ss[i] );
+ }
+
+ return( bvs );
+}
+
+
+/*
+ * Convert a struct berval * array into a char * array.
+ * Always succeeds.
+ */
+char **
+passthru_bervals2strs( struct berval **bvs )
+{
+ int i;
+ char **strs;
+
+ if ( bvs == NULL || bvs[0] == NULL ) {
+ return( NULL );
+ }
+
+ for ( i = 0; bvs[i] != NULL; ++i ) {
+ ;
+ }
+
+ strs = (char **)slapi_ch_calloc( i + 1, sizeof( char * ));
+ for ( i = 0; bvs[i] != NULL; ++i ) {
+ strs[i] = slapi_ch_strdup( bvs[i]->bv_val );
+ }
+
+ return( strs );
+}
+
+
+void
+passthru_free_bervals( struct berval **bvs )
+{
+ int i;
+
+ if ( bvs != NULL ) {
+ for ( i = 0; bvs[ i ] != NULL; ++i ) {
+ slapi_ch_free( (void **)&bvs[ i ] );
+ }
+ }
+ slapi_ch_free( (void **)&bvs );
+}
+
+
+char *
+passthru_urlparse_err2string( int err )
+{
+ char *s;
+
+ switch( err ) {
+ case 0:
+ s = "no error";
+ break;
+ case LDAP_URL_ERR_NOTLDAP:
+ s = "missing ldap:// or ldaps://";
+ break;
+ case LDAP_URL_ERR_NODN:
+ s = "missing suffix";
+ break;
+ case LDAP_URL_ERR_BADSCOPE:
+ s = "invalid search scope";
+ break;
+ case LDAP_URL_ERR_MEM:
+ s = "unable to allocate memory";
+ break;
+ case LDAP_URL_ERR_PARAM:
+ s = "bad parameter to an LDAP URL function";
+ break;
+ }
+
+ return( s );
+}
diff --git a/ldap/servers/plugins/presence/Makefile b/ldap/servers/plugins/presence/Makefile
new file mode 100644
index 00000000..9b15bc96
--- /dev/null
+++ b/ldap/servers/plugins/presence/Makefile
@@ -0,0 +1,85 @@
+#
+# BEGIN COPYRIGHT BLOCK
+# Copyright 2001 Sun Microsystems, Inc.
+# Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+# All rights reserved.
+# END COPYRIGHT BLOCK
+#
+LDAP_SRC = ../../..
+MCOM_ROOT = ../../../../..
+
+NOSTDCLEAN=true # don't let nsconfig.mk define target clean
+NOSTDSTRIP=true # don't let nsconfig.mk define target strip
+NSPR20=true # probably should be defined somewhere else (not sure where)
+
+OBJDEST = $(OBJDIR)/lib/libpresence
+LIBDIR = $(LIB_RELDIR)
+
+include $(MCOM_ROOT)/ldapserver/nsdefs.mk
+include $(MCOM_ROOT)/ldapserver/nsconfig.mk
+include $(LDAP_SRC)/nsldap.mk
+
+ifeq ($(ARCH), WINNT)
+DEF_FILE:=./presence.def
+endif
+
+PRESENCE_OBJS = presence.o
+OBJS = $(addprefix $(OBJDEST)/, $(PRESENCE_OBJS))
+
+PRESENCE_DLL = presence-plugin
+
+INCLUDES += -I../http -I../../slapd -I../../../include
+CFLAGS+=$(SLCFLAGS) -DSLAPD_LOGGING
+
+ifeq ($(ARCH), WINNT)
+EXTRA_LIBS_DEP += $(LIBSLAPD) $(NSPR_DEP) $(LDAPSDK_DEP)
+EXTRA_LIBS_DEP += $(LDAP_COMMON_LIBS_DEP)
+EXTRA_LIBS += $(NSPRLINK) $(LIBSLAPD) $(LDAP_SDK_LIBLDAP_DLL)
+EXTRA_LIBS += $(LDAP_COMMON_LIBS)
+PRESENCE_DLL_OBJ = $(addprefix $(OBJDEST)/, dllmain.o)
+endif
+
+ifeq ($(ARCH), AIX)
+EXTRA_LIBS_DEP += $(LIBSLAPD) $(NSPR_DEP) $(LDAPSDK_DEP)
+EXTRA_LIBS_DEP += $(LDAP_COMMON_LIBS_DEP)
+EXTRA_LIBS += $(LIBSLAPDLINK) $(NSPRLINK) $(LDAP_SDK_LIBLDAP_DLL)
+EXTRA_LIBS += $(LDAP_COMMON_LIBS)
+LD=ld
+endif
+
+ifeq ($(ARCH), HPUX)
+EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAPSDK_DEP) $(NSPR_DEP) $(SECURITY_DEP)
+EXTRA_LIBS_DEP += $(LDAP_COMMON_LIBS_DEP)
+EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(SECURITYLINK) $(NSPRLINK) $(ICULINK)
+EXTRA_LIBS += $(LDAP_COMMON_LIBS)
+endif
+
+PRESENCE= $(addprefix $(LIBDIR)/, $(PRESENCE_DLL).$(DLL_SUFFIX))
+
+clientSDK:
+
+all: $(OBJDEST) $(LIBDIR) $(PRESENCE)
+
+ifeq ($(ARCH), WINNT)
+$(PRESENCE): $(OBJS) $(PRESENCE_DLL_OBJ) $(DEF_FILE)
+ $(LINK_DLL) $(PRESENCE_DLL_OBJ) $(EXTRA_LIBS) /DEF:$(DEF_FILE)
+else
+$(PRESENCE): $(OBJS) $(PRESENCE_DLL_OBJ)
+ $(LINK_DLL) $(PRESENCE_DLL_OBJ) $(EXTRA_LIBS)
+endif
+
+
+veryclean: clean
+
+clean:
+ $(RM) $(OBJS)
+ifeq ($(ARCH), WINNT)
+ $(RM) $(PRESENCE_DLL_OBJ)
+endif
+ $(RM) $(PRESENCE)
+
+$(OBJDEST):
+ $(MKDIR) $(OBJDEST)
+
+$(LIBDIR):
+ $(MKDIR) $(LIBDIR)
diff --git a/ldap/servers/plugins/presence/dllmain.c b/ldap/servers/plugins/presence/dllmain.c
new file mode 100644
index 00000000..fabf8677
--- /dev/null
+++ b/ldap/servers/plugins/presence/dllmain.c
@@ -0,0 +1,96 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/*
+ * Microsoft Windows specifics for BACK-LDBM DLL
+ */
+#include "ldap.h"
+#include "lber.h"
+
+
+#ifdef _WIN32
+/* Lifted from Q125688
+ * How to Port a 16-bit DLL to a Win32 DLL
+ * on the MSVC 4.0 CD
+ */
+BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved)
+{
+ WSADATA wsadata;
+
+ switch (fdwReason)
+ {
+ case DLL_PROCESS_ATTACH:
+ /* Code from LibMain inserted here. Return TRUE to keep the
+ DLL loaded or return FALSE to fail loading the DLL.
+
+ You may have to modify the code in your original LibMain to
+ account for the fact that it may be called more than once.
+ You will get one DLL_PROCESS_ATTACH for each process that
+ loads the DLL. This is different from LibMain which gets
+ called only once when the DLL is loaded. The only time this
+ is critical is when you are using shared data sections.
+ If you are using shared data sections for statically
+ allocated data, you will need to be careful to initialize it
+ only once. Check your code carefully.
+
+ Certain one-time initializations may now need to be done for
+ each process that attaches. You may also not need code from
+ your original LibMain because the operating system may now
+ be doing it for you.
+ */
+ /*
+ * 16 bit code calls UnlockData()
+ * which is mapped to UnlockSegment in windows.h
+ * in 32 bit world UnlockData is not defined anywhere
+ * UnlockSegment is mapped to GlobalUnfix in winbase.h
+ * and the docs for both UnlockSegment and GlobalUnfix say
+ * ".. function is oboslete. Segments have no meaning
+ * in the 32-bit environment". So we do nothing here.
+ */
+
+ if( errno = WSAStartup(0x0101, &wsadata ) != 0 )
+ return FALSE;
+
+ break;
+
+ case DLL_THREAD_ATTACH:
+ /* Called each time a thread is created in a process that has
+ already loaded (attached to) this DLL. Does not get called
+ for each thread that exists in the process before it loaded
+ the DLL.
+
+ Do thread-specific initialization here.
+ */
+ break;
+
+ case DLL_THREAD_DETACH:
+ /* Same as above, but called when a thread in the process
+ exits.
+
+ Do thread-specific cleanup here.
+ */
+ break;
+
+ case DLL_PROCESS_DETACH:
+ /* Code from _WEP inserted here. This code may (like the
+ LibMain) not be necessary. Check to make certain that the
+ operating system is not doing it for you.
+ */
+ WSACleanup();
+
+ break;
+ }
+ /* The return value is only used for DLL_PROCESS_ATTACH; all other
+ conditions are ignored. */
+ return TRUE; // successful DLL_PROCESS_ATTACH
+}
+#else
+int CALLBACK
+LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine )
+{
+ /*UnlockData( 0 );*/
+ return( 1 );
+}
+#endif
diff --git a/ldap/servers/plugins/presence/images/aim-offline.gif b/ldap/servers/plugins/presence/images/aim-offline.gif
new file mode 100644
index 00000000..7403ea29
--- /dev/null
+++ b/ldap/servers/plugins/presence/images/aim-offline.gif
Binary files differ
diff --git a/ldap/servers/plugins/presence/images/aim-online.gif b/ldap/servers/plugins/presence/images/aim-online.gif
new file mode 100644
index 00000000..d90c2910
--- /dev/null
+++ b/ldap/servers/plugins/presence/images/aim-online.gif
Binary files differ
diff --git a/ldap/servers/plugins/presence/images/icq-disabled.gif b/ldap/servers/plugins/presence/images/icq-disabled.gif
new file mode 100644
index 00000000..78b748cd
--- /dev/null
+++ b/ldap/servers/plugins/presence/images/icq-disabled.gif
Binary files differ
diff --git a/ldap/servers/plugins/presence/images/icq-offline.gif b/ldap/servers/plugins/presence/images/icq-offline.gif
new file mode 100644
index 00000000..40b35c16
--- /dev/null
+++ b/ldap/servers/plugins/presence/images/icq-offline.gif
Binary files differ
diff --git a/ldap/servers/plugins/presence/images/icq-online.gif b/ldap/servers/plugins/presence/images/icq-online.gif
new file mode 100644
index 00000000..bd5452c1
--- /dev/null
+++ b/ldap/servers/plugins/presence/images/icq-online.gif
Binary files differ
diff --git a/ldap/servers/plugins/presence/images/yahoo-offline.gif b/ldap/servers/plugins/presence/images/yahoo-offline.gif
new file mode 100644
index 00000000..315a2261
--- /dev/null
+++ b/ldap/servers/plugins/presence/images/yahoo-offline.gif
Binary files differ
diff --git a/ldap/servers/plugins/presence/images/yahoo-online.gif b/ldap/servers/plugins/presence/images/yahoo-online.gif
new file mode 100644
index 00000000..1c2b16d8
--- /dev/null
+++ b/ldap/servers/plugins/presence/images/yahoo-online.gif
Binary files differ
diff --git a/ldap/servers/plugins/presence/presence.c b/ldap/servers/plugins/presence/presence.c
new file mode 100644
index 00000000..6c75e745
--- /dev/null
+++ b/ldap/servers/plugins/presence/presence.c
@@ -0,0 +1,1204 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+/**
+ * IM Presence plug-in
+ */
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include "portable.h"
+#include "nspr.h"
+#include "slapi-plugin.h"
+#include "slapi-private.h"
+#include "dirlite_strings.h"
+#include "dirver.h"
+#include "vattr_spi.h"
+#include "plhash.h"
+#include "ldif.h"
+
+#include "http_client.h"
+
+/* get file mode flags for unix */
+#ifndef _WIN32
+#include <sys/stat.h>
+#endif
+
+/*** from proto-slap.h ***/
+
+int slapd_log_error_proc( char *subsystem, char *fmt, ... );
+
+/*** from ldaplog.h ***/
+
+/* edited ldaplog.h for LDAPDebug()*/
+#ifndef _LDAPLOG_H
+#define _LDAPLOG_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define LDAP_DEBUG_TRACE 0x00001 /* 1 */
+#define LDAP_DEBUG_ANY 0x04000 /* 16384 */
+#define LDAP_DEBUG_PLUGIN 0x10000 /* 65536 */
+
+/* debugging stuff */
+# ifdef _WIN32
+ extern int *module_ldap_debug;
+# define LDAPDebugLevelIsSet( level ) ( *module_ldap_debug & level )
+# else /* _WIN32 */
+ extern int slapd_ldap_debug;
+# define LDAPDebugLevelIsSet( level ) ( slapd_ldap_debug & level )
+# endif /* Win32 */
+
+#define LDAPDebug( level, fmt, arg1, arg2, arg3 ) \
+ { \
+ if ( LDAPDebugLevelIsSet( level )) { \
+ slapd_log_error_proc( NULL, fmt, arg1, arg2, arg3 ); \
+ } \
+ }
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _LDAP_H */
+
+#define PRESENCE_PLUGIN_SUBSYSTEM "presence-plugin"
+#define PRESENCE_PLUGIN_VERSION 0x00050050
+
+/**
+ * this may become unnecessary when we are able to get
+ * the plug-in DN dynamically (pete?)
+ */
+#define PRESENCE_DN "cn=Presence,cn=plugins,cn=config" /* temporary */
+
+#define PRESENCE_SUCCESS 0
+#define PRESENCE_FAILURE -1
+
+/**
+ * Presence vendor specific config parameters
+ */
+
+#define NS_IM_ID "nsIM-ID"
+
+#define NS_IM_URL_TEXT "nsIM-URLText"
+#define NS_IM_URL_GRAPHIC "nsIM-URLGraphic"
+
+#define NS_IM_ON_VALUE_MAP_TEXT "nsIM-OnValueMapText"
+#define NS_IM_OFF_VALUE_MAP_TEXT "nsIM-OffValueMapText"
+
+#define NS_IM_ON_VALUE_MAP_GRAPHIC "nsIM-OnValueMapGraphic"
+#define NS_IM_OFF_VALUE_MAP_GRAPHIC "nsIM-OffValueMapGraphic"
+#define NS_IM_DISABLED_VALUE_MAP_GRAPHIC "nsIM-disabledValueMapGraphic"
+
+#define NS_IM_REQUEST_METHOD "nsIM-RequestMethod"
+
+#define NS_IM_URL_TEXT_RETURN_TYPE "nsIM-URLTextReturnType"
+#define NS_IM_URL_GRAPHIC_RETURN_TYPE "nsIM-URLGraphicReturnType"
+
+#define NS_IM_STATUS_TEXT "nsIM-StatusText"
+#define NS_IM_STATUS_GRAPHIC "nsIM-StatusGraphic"
+
+#define PRESENCE_STRING 1
+#define PRESENCE_BINARY 2
+
+#define PRESENCE_TEXT_RETURN_TYPE "TEXT"
+#define PRESENCE_BINARY_RETURN_TYPE "BINARY"
+
+#define PRESENCE_REQUEST_METHOD_GET "GET"
+#define PRESENCE_REQUEST_METHOD_REDIRECT "REDIRECT"
+
+#define PRESENCE_RETURNED_ON_TEXT "ONLINE"
+#define PRESENCE_RETURNED_OFF_TEXT "OFFLINE"
+#define PRESENCE_RETURNED_ERROR_TEXT "ERROR"
+
+static Slapi_PluginDesc pdesc = { "IM Presence",
+ PLUGIN_MAGIC_VENDOR_STR,
+ PRODUCTTEXT,
+ "presence plugin" };
+
+/**
+ * struct used to pass the argument to PL_Enumerator Callback
+ */
+struct _vattrtypes
+{
+ Slapi_Entry *entry;
+ vattr_type_list_context *context;
+};
+
+/**
+ * This structure holds the mapping between the virtual attributes and
+ * the IM IDs. This information is used to find out whether this plugin
+ * should service the attributes it was asked to. Also, it stores the
+ * syntax of the attribute. 1 is String and 2 is binary.
+ */
+struct _vattrmap {
+ char *imID;
+ int syntax;
+};
+typedef struct _vattrmap _Vmap;
+
+/**
+ * struct to store the config values for each presence vendor
+ */
+struct _defs {
+ char *textURL;
+ char *graphicURL;
+ char *onTextMap;
+ char *offTextMap;
+ Slapi_Attr *onGraphicMap;
+ Slapi_Attr *offGraphicMap;
+ Slapi_Attr *disabledGraphicMap;
+ char *requestMethod;
+ char *textReturnType;
+ char *graphicReturnType;
+};
+typedef struct _defs _ConfigEntry;
+
+static vattr_sp_handle *_VattrHandle = NULL;
+static void *_PluginID = NULL;
+static void *_PluginDN = NULL;
+static PLHashTable *_IdVattrMapTable = NULL;
+static PLHashTable *_IdConfigMapTable = NULL;
+static void **_HttpAPI = NULL;
+
+/**
+ *
+ * Presence plug-in management functions
+ *
+ */
+int presence_init(Slapi_PBlock *pb);
+int presence_start(Slapi_PBlock *pb);
+int presence_close(Slapi_PBlock *pb);
+
+/**
+ *
+ * Vattr operation callbacks functions
+ *
+ */
+static int presence_vattr_get(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, char *type, Slapi_ValueSet** results,int *type_name_disposition, char** actual_type_name, int flags, int *free_flags, void *hint);
+static int presence_vattr_compare(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, char *type, Slapi_Value *test_this, int* result, int flags, void *hint);
+static int presence_vattr_types(vattr_sp_handle *handle,Slapi_Entry *e,vattr_type_list_context *type_context,int flags);
+
+/**
+ *
+ * Local operation functions
+ *
+ */
+static int loadPluginConfig();
+static int parseConfigEntry(Slapi_Entry *e);
+static int imIDExists(Slapi_Entry *e, char *type, char **value, _Vmap **map, _ConfigEntry **entry);
+static int makeHttpRequest(char *id, _Vmap *map, _ConfigEntry *info, char **buf, int *size);
+static char * replaceIdWithValue(char *str, char *id, char *value);
+static int setIMStatus(char *id, _Vmap *map, _ConfigEntry *info, char *returnedBUF, int size, Slapi_ValueSet **results);
+static int setTypes(PLHashEntry *he, PRIntn i, void *arg);
+
+static void deleteMapTables();
+static PRIntn destroyHashEntry(PLHashEntry *he, PRIntn index, void *arg);
+static void logGraphicAttributeValue( Slapi_Attr *attr, const char *attrname );
+static void toLowerCase(char* str);
+
+/**
+ * utility function
+ */
+void printMapTable();
+PRIntn printIdVattrMapTable(PLHashEntry *he, PRIntn i, void *arg);
+PRIntn printIdConfigMapTable(PLHashEntry *he, PRIntn i, void *arg);
+
+/**
+ * set the debug level
+ */
+#ifdef _WIN32
+int *module_ldap_debug = 0;
+
+void plugin_init_debug_level(int *level_ptr)
+{
+ module_ldap_debug = level_ptr;
+}
+#endif
+
+/**
+ *
+ * Get the presence plug-in version
+ *
+ */
+int presence_version()
+{
+ return PRESENCE_PLUGIN_VERSION;
+}
+
+/**
+ * Plugin identity mgmt
+ */
+void setPluginID(void * pluginID)
+{
+ _PluginID=pluginID;
+}
+
+void * getPluginID()
+{
+ return _PluginID;
+}
+
+void setPluginDN(void *pluginDN)
+{
+ _PluginDN = pluginDN;
+}
+
+void * getPluginDN()
+{
+ return _PluginDN;
+}
+
+/*
+ presence_init
+ -------------
+ adds our callbacks to the list
+*/
+int presence_init( Slapi_PBlock *pb )
+{
+ int status = PRESENCE_SUCCESS;
+ char * plugin_identity=NULL;
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "--> presence_init -- BEGIN\n",0,0,0);
+
+ /**
+ * Store the plugin identity for later use.
+ * Used for internal operations
+ */
+
+ slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &plugin_identity);
+ PR_ASSERT (plugin_identity);
+ setPluginID(plugin_identity);
+
+ if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION,
+ SLAPI_PLUGIN_VERSION_01 ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN,
+ (void *) presence_start ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN,
+ (void *) presence_close ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION,
+ (void *)&pdesc ) != 0 )
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, PRESENCE_PLUGIN_SUBSYSTEM,
+ "presence_init: failed to register plugin\n" );
+ status = PRESENCE_FAILURE;
+ }
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- presence_init -- END\n",0,0,0);
+ return status;
+}
+
+/*
+ presence_start
+ --------------
+ This function registers the computed attribute evaluator
+ and loads the configuration parameters in the local cache.
+ It is called after presence_init.
+*/
+int presence_start( Slapi_PBlock *pb )
+{
+ char * plugindn = NULL;
+ char * httpRootDir = NULL;
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "--> presence_start -- begin\n",0,0,0);
+
+ if(slapi_apib_get_interface(HTTP_v1_0_GUID, &_HttpAPI))
+ {
+ /**
+ * error cannot proceeed
+ */
+ return PRESENCE_FAILURE;
+ }
+
+ /**
+ * register our vattr callbacks
+ */
+ if (slapi_vattrspi_register((vattr_sp_handle **)&_VattrHandle,
+ presence_vattr_get,
+ presence_vattr_compare,
+ presence_vattr_types) != 0)
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, PRESENCE_PLUGIN_SUBSYSTEM,
+ "presence_start: cannot register as service provider\n" );
+ return PRESENCE_FAILURE;
+ }
+
+ /**
+ * Get the plug-in target dn from the system
+ * and store it for future use. This should avoid
+ * hardcoding of DN's in the code.
+ */
+ slapi_pblock_get(pb, SLAPI_TARGET_DN, &plugindn);
+ if (plugindn == NULL || strlen(plugindn) == 0)
+ {
+ /**
+ * This is not required as the above statement
+ * should work and give you a valid DN for this
+ * plugin. ??? remove it later
+ */
+ plugindn = PRESENCE_DN;
+ }
+ setPluginDN(plugindn);
+
+ /**
+ * Load the config info for our plug-in in memory
+ * In the 6.0 release this information will be stored
+ * statically and if any change is done to this info a server
+ * restart is necessary :-(. Probably if time permits then
+ * state change plug-in would be used to notify the state
+ * change. We also register the virtual attributes we are
+ * interested in here.
+ */
+ if (loadPluginConfig() != PRESENCE_SUCCESS)
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, PRESENCE_PLUGIN_SUBSYSTEM,
+ "presence_start: unable to load plug-in configuration\n" );
+ return PRESENCE_FAILURE;
+ }
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "presence: ready for service\n",0,0,0);
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- presence_start -- end\n",0,0,0);
+
+ return PRESENCE_SUCCESS;
+}
+
+/*
+ presence_close
+ --------------
+ closes down the cache
+*/
+int presence_close( Slapi_PBlock *pb )
+{
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "--> presence_close\n",0,0,0);
+
+ deleteMapTables();
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- presence_close\n",0,0,0);
+
+ return PRESENCE_SUCCESS;
+}
+
+static int presence_vattr_get(vattr_sp_handle *handle,
+ vattr_context *c,
+ Slapi_Entry *e,
+ char *type,
+ Slapi_ValueSet** results,
+ int *type_name_disposition,
+ char** actual_type_name,
+ int flags,
+ int *free_flags,
+ void *hint)
+{
+
+ int status = PRESENCE_SUCCESS;
+ char *id = NULL;
+ char *returnedBUF = NULL;
+ int size = 0;
+ _Vmap *map = NULL;
+ _ConfigEntry *info = NULL;
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "--> presence_vattr_get \n",0,0,0);
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Type=[%s] \n",type,0,0);
+
+ if (imIDExists(e, type, &id, &map, &info) != PRESENCE_SUCCESS)
+ {
+ /**
+ * we didn't find any valid matching nsimid in this
+ * entry so since we cannot process a request without
+ * a valid nsimid we just return.
+ */
+ status = PRESENCE_FAILURE;
+ goto cleanup;
+ }
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> ID=[%s] \n",id,0,0);
+
+ /**
+ * Now since we got a valid id we do a quick schema check
+ * if schema checking is on to make sure that there is no
+ * schema violation ?
+ */
+ /* do_schema_check() */
+
+ /**
+ * At this stage we have a valid attribute and we have to
+ * get its value from the IM Server. so make an Http request
+ * depending on whether it is a request for Text or Graphic
+ */
+
+ status = makeHttpRequest(id, map, info, &returnedBUF, &size);
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> size=[%d] \n",size,0,0);
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> buffer=[%s]\n",(returnedBUF) ? returnedBUF : "NULL",0,0);
+
+
+ if(status == PRESENCE_SUCCESS)
+ {
+ status = setIMStatus(id, map, info, returnedBUF, size, results);
+ }
+ else
+ {
+ /**
+ * Report all HTTP failures as a single predefined value of the
+ * attribute
+ */
+ Slapi_Value *value =
+ slapi_value_new_string(PRESENCE_RETURNED_ERROR_TEXT);
+ if (!*results) {
+ *results = slapi_valueset_new();
+ }
+ slapi_valueset_add_value(*results, value);
+ slapi_value_free(&value); /* slapi_valueset_add_value copies value */
+ /**
+ * It's a success only in the sense that we are returning a value
+ */
+ status = PRESENCE_SUCCESS;
+ }
+ if(status == PRESENCE_SUCCESS)
+ {
+ *free_flags = SLAPI_VIRTUALATTRS_RETURNED_COPIES;
+ *actual_type_name = slapi_ch_strdup(type);
+ *type_name_disposition = SLAPI_VIRTUALATTRS_TYPE_NAME_MATCHED_EXACTLY_OR_ALIAS;
+ }
+
+cleanup:
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Processed ID=[%s] \n",id,0,0);
+ if (id != NULL ) {
+ slapi_ch_free((void **)&id);
+ }
+ if (returnedBUF != NULL ) {
+ PR_Free(returnedBUF);
+ }
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- presence_vattr_get \n",0,0,0);
+ return status;
+}
+
+
+static int presence_vattr_compare(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, char *type, Slapi_Value *test_this, int* result, int flags, void *hint)
+{
+ int status = PRESENCE_SUCCESS;
+ /**
+ * not yet implemented ???
+ */
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "--> presence_vattr_compare \n",0,0,0);
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- presence_vattr_compare \n",0,0,0);
+
+ return status;
+}
+
+static int presence_vattr_types(vattr_sp_handle *handle,Slapi_Entry *e,vattr_type_list_context *type_context,int flags)
+{
+ int status = PRESENCE_SUCCESS;
+ struct _vattrtypes args;
+ args.entry = e;
+ args.context = type_context;
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "--> presence_vattr_types\n",0,0,0);
+
+ PL_HashTableEnumerateEntries(_IdVattrMapTable, setTypes, &args);
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- presence_vattr_types\n",0,0,0);
+ return status;
+}
+
+static int loadPluginConfig()
+{
+ int status = PRESENCE_SUCCESS;
+ int result;
+ int i;
+ Slapi_PBlock *search_pb;
+ Slapi_Entry **entries = NULL;
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "--> loadPluginConfig\n",0,0,0);
+
+ search_pb = slapi_pblock_new();
+
+ slapi_search_internal_set_pb(search_pb, PRESENCE_DN, LDAP_SCOPE_ONELEVEL,
+ "objectclass=*", NULL, 0, NULL, NULL, getPluginID(), 0);
+ slapi_search_internal_pb(search_pb);
+ slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_RESULT, &result);
+
+ if (status != PRESENCE_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, PRESENCE_PLUGIN_SUBSYSTEM,
+ "Error getting level1 presence configurations<%s>\n", getPluginDN());
+ status = PRESENCE_FAILURE;
+ goto cleanup;
+ }
+
+ slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries);
+ if (NULL == entries || entries[0] == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, PRESENCE_PLUGIN_SUBSYSTEM,
+ "No entries found for <%s>\n", getPluginDN());
+
+ status = PRESENCE_FAILURE;
+ goto cleanup;
+ }
+
+ _IdVattrMapTable = PL_NewHashTable( 0,
+ PL_HashString,
+ PL_CompareStrings,
+ PL_CompareValues,
+ NULL,
+ NULL
+ );
+
+ _IdConfigMapTable = PL_NewHashTable(0,
+ PL_HashString,
+ PL_CompareStrings,
+ PL_CompareValues,
+ NULL,
+ NULL
+ );
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "--> parseConfigEntry \n",0,0,0);
+
+ for (i = 0; (entries[i] != NULL); i++)
+ {
+ status = parseConfigEntry(entries[i]);
+ if (status != PRESENCE_SUCCESS)
+ {
+ deleteMapTables();
+ goto cleanup;
+ }
+ }
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- parseConfigEntry \n",0,0,0);
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- loadPluginConfig\n",0,0,0);
+
+cleanup:
+ slapi_free_search_results_internal(search_pb);
+ slapi_pblock_destroy(search_pb);
+ return status;
+}
+
+static int parseConfigEntry(Slapi_Entry *e)
+{
+ char *key = NULL;
+ char *value = NULL;
+ _ConfigEntry *entry = NULL;
+ _Vmap *map = NULL;
+ Slapi_Attr *attr = NULL;
+
+ key = slapi_entry_attr_get_charptr(e, NS_IM_ID);
+ if (!key) {
+ /**
+ * IM Id not defined in the config, unfortunately
+ * cannot do anything without it so better not to
+ * load the plug-in.
+ */
+ return PRESENCE_FAILURE;
+ }
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> key [%s] \n",key,0,0);
+ /**
+ * Now create the config entry which will hold all the
+ * attributes of a presence vendor
+ */
+ entry = (_ConfigEntry*) slapi_ch_calloc(1, sizeof(_ConfigEntry));
+
+ /**
+ * Next 2 are the virtual attributes for which this plug-in
+ * is responsible. Register them with the vattr system so
+ * that the system can call us whenever their
+ * values are requested. Also update these entries in the
+ * map table for later access.
+ */
+ value = slapi_entry_attr_get_charptr(e, NS_IM_STATUS_TEXT);
+ if (value) {
+ slapi_vattrspi_regattr(_VattrHandle, value, "", NULL);
+ map = (_Vmap*) slapi_ch_calloc(1, sizeof(_Vmap));
+ map->imID = key;
+ map->syntax = PRESENCE_STRING;
+ toLowerCase(value);
+ PL_HashTableAdd(_IdVattrMapTable, value, map);
+ }
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> nsIMStatusText [%s] \n",value,0,0);
+
+ value = slapi_entry_attr_get_charptr(e, NS_IM_STATUS_GRAPHIC);
+ if (value) {
+ slapi_vattrspi_regattr(_VattrHandle, value, "", NULL);
+ map = (_Vmap*) slapi_ch_calloc(1, sizeof(_Vmap));
+ map->imID = key;
+ map->syntax = PRESENCE_BINARY;
+ toLowerCase(value);
+ PL_HashTableAdd(_IdVattrMapTable, value, map);
+ }
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> nsIMStatusGraphic [%s] \n",value,0,0);
+
+ value = slapi_entry_attr_get_charptr(e, NS_IM_URL_TEXT);
+ if (value) {
+ entry->textURL = value;
+ }
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> nsIMURLText [%s] \n",value,0,0);
+
+ value = slapi_entry_attr_get_charptr(e, NS_IM_URL_GRAPHIC);
+ if (value) {
+ entry->graphicURL = value;
+ }
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> nsIMStatusGraphic [%s] \n",value,0,0);
+
+ value = slapi_entry_attr_get_charptr(e, NS_IM_ON_VALUE_MAP_TEXT);
+ if (value) {
+ entry->onTextMap = value;
+ }
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> nsIMOnValueMapText [%s] \n",value,0,0);
+
+ value = slapi_entry_attr_get_charptr(e, NS_IM_OFF_VALUE_MAP_TEXT);
+ if (value) {
+ entry->offTextMap = value;
+ }
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> nsIMOffValueMapText [%s] \n",value,0,0);
+
+ /**
+ * Next 3 are binary syntax types so needs special handling
+ */
+ slapi_entry_attr_find(e, NS_IM_ON_VALUE_MAP_GRAPHIC, &attr);
+ if (attr) {
+ entry->onGraphicMap = slapi_attr_dup(attr);
+ logGraphicAttributeValue(attr,NS_IM_ON_VALUE_MAP_GRAPHIC);
+ }
+
+ slapi_entry_attr_find(e, NS_IM_OFF_VALUE_MAP_GRAPHIC, &attr);
+ if (attr) {
+ entry->offGraphicMap = slapi_attr_dup(attr);
+ logGraphicAttributeValue(attr,NS_IM_OFF_VALUE_MAP_GRAPHIC);
+ }
+
+ slapi_entry_attr_find(e, NS_IM_DISABLED_VALUE_MAP_GRAPHIC, &attr);
+ if (attr) {
+ entry->disabledGraphicMap = slapi_attr_dup(attr);
+ logGraphicAttributeValue(attr,NS_IM_DISABLED_VALUE_MAP_GRAPHIC);
+ }
+
+ value = slapi_entry_attr_get_charptr(e, NS_IM_REQUEST_METHOD);
+ if (value) {
+ entry->requestMethod = value;
+ }
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> nsIMRequestMethod [%s] \n",value,0,0);
+
+ value = slapi_entry_attr_get_charptr(e, NS_IM_URL_TEXT_RETURN_TYPE);
+ if (value) {
+ entry->textReturnType = value;
+ }
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> nsIMURLTextReturnType [%s] \n",value,0,0);
+
+ value = slapi_entry_attr_get_charptr(e, NS_IM_URL_GRAPHIC_RETURN_TYPE);
+ if (value) {
+ entry->graphicReturnType = value;
+ }
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> nsIMURLGraphicReturnType [%s] \n",value,0,0);
+
+ /**
+ * Finally add the entry to the map table
+ */
+ PL_HashTableAdd(_IdConfigMapTable, key, entry);
+
+ return PRESENCE_SUCCESS;
+}
+
+/**
+ * this function goes thru the valid stored ids
+ * and return the correct one for which we have to
+ * do further processing
+ */
+static int imIDExists(Slapi_Entry *e, char *type, char **value, _Vmap **map, _ConfigEntry **entry)
+{
+ int status = PRESENCE_SUCCESS;
+ char *tValue = NULL;
+ _ConfigEntry *tEntry = NULL;
+ _Vmap *tMap = NULL;
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "--> imIDExists \n",0,0,0);
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Type [%s] \n",type,0,0);
+
+ /**
+ * The public function PL_HashTableLookup modifies the
+ * the table while reading. so using this private function
+ * which just does a lookup and doesn't modifies the
+ * hashtable
+ */
+ toLowerCase(type);
+ tMap = PL_HashTableLookupConst(_IdVattrMapTable, type);
+ if (!tMap)
+ {
+ /**
+ * this should not happen but no harm we just return
+ */
+ status = PRESENCE_FAILURE;
+ slapi_log_error(SLAPI_LOG_FATAL, PRESENCE_PLUGIN_SUBSYSTEM,
+ "No hashtable for vattr types\n");
+ goto bail;
+ }
+ /**
+ * We found a matching id in the map table
+ * now see if that id exists in the Slapi_Entry
+ */
+ tValue = slapi_entry_attr_get_charptr(e, tMap->imID);
+ if (!tValue)
+ {
+ /**
+ * we don't do anything here but just return
+ */
+ status = PRESENCE_FAILURE;
+ goto bail;
+ }
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Value [%s] \n",tValue,0,0);
+
+ tEntry = PL_HashTableLookupConst(_IdConfigMapTable, tMap->imID);
+ *value = tValue;
+ *entry = tEntry;
+ *map = tMap;
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- imIDExists \n",0,0,0);
+
+bail:
+ return status;
+}
+
+static int makeHttpRequest(char *id, _Vmap *map, _ConfigEntry *info, char **BUF, int *size)
+{
+ int status = PRESENCE_SUCCESS;
+ char *buf = NULL;
+ char *url = NULL;
+ char *urltosend = NULL;
+ int bytesRead;
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "--> makeHttpRequest:: \n",0,0,0);
+
+ if (map->syntax == PRESENCE_STRING) {
+ url = info->textURL;
+ } else {
+ url = info->graphicURL;
+ }
+ if (url == NULL) {
+ status = PRESENCE_FAILURE;
+ goto bail;
+ }
+ urltosend = replaceIdWithValue(url, map->imID, id);
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> URL [%s] \n",urltosend,0,0);
+ /**
+ * make an actual HTTP call now
+ */
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> RequestMethod [%s] \n", info->requestMethod,0,0);
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Syntax [%d] \n", map->syntax,0,0);
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> TextReturnType [%s] \n", info->textReturnType,0,0);
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> GraphicReturnType [%s] \n", info->graphicReturnType,0,0);
+ if (!strcasecmp(info->requestMethod, PRESENCE_REQUEST_METHOD_GET)) {
+ if (map->syntax == PRESENCE_STRING) {
+ if (!strcasecmp(info->textReturnType, PRESENCE_TEXT_RETURN_TYPE)) {
+ status = http_get_text(_HttpAPI, urltosend, &buf, &bytesRead);
+ } else {
+ status = http_get_binary(_HttpAPI, urltosend, &buf, &bytesRead);
+ }
+ } else {
+ if (!strcasecmp(info->graphicReturnType, PRESENCE_TEXT_RETURN_TYPE)) {
+ status = http_get_text(_HttpAPI, urltosend, &buf, &bytesRead);
+ } else {
+ status = http_get_binary(_HttpAPI, urltosend, &buf, &bytesRead);
+ }
+ }
+ } else if (!strcasecmp(info->requestMethod, PRESENCE_REQUEST_METHOD_REDIRECT)) {
+ status = http_get_redirected_uri(_HttpAPI, urltosend, &buf, &bytesRead);
+ } else {
+ /**
+ * error : unknown method
+ * probably we should check at the time of loading
+ * of the plugin itself that the config values are
+ * properly checked and throw warning/errors in case
+ * of any invalid entry
+ */
+ slapi_log_error(SLAPI_LOG_FATAL, PRESENCE_PLUGIN_SUBSYSTEM,
+ "Unknown request type <%s>\n", info->requestMethod);
+ status = PRESENCE_FAILURE;
+ goto bail;
+ }
+ if (buf && status == PRESENCE_SUCCESS)
+ {
+ *BUF = buf;
+ *size = bytesRead;
+ }
+
+bail:
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- makeHttpRequest:: <%d>\n",status,0,0);
+
+ slapi_ch_free((void**)&urltosend);
+ return status;
+}
+
+/**
+ * This function replaces the occurrence of $ns[<vendor>]imid with its
+ * actual value
+ * e.g.
+ * URL : http://opi.yahoo.com/online?u=$nsyimid
+ * after replacing
+ * newURL : http://opi.yahoo.com/online?u=srajam
+ */
+static char * replaceIdWithValue(char *str, char *id, char *value)
+{
+ int i=0;
+ int k=0;
+ char *newstr = NULL;
+ char c;
+ if (!str || !id || !value)
+ {
+ return NULL;
+ }
+ /* extra space for userids */
+ newstr = (char *)slapi_ch_malloc(strlen(str) + strlen(value));
+ while ((c=str[i]) != '\0')
+ {
+ if (c == '$')
+ {
+ int j = 0;
+ i++; /*skip one char */
+ /**
+ * we found the begining of the string to be
+ * substituted. Now skip the chars we want to replace
+ */
+ while (str[i] != '\0' && id[j] != '\0' &&
+ (toupper(str[i]) == toupper(id[j])))
+ {
+ i++;
+ j++;
+ }
+ j=0;
+ while (value[j] != '\0')
+ {
+ newstr[k++] = value[j++];
+ }
+ }
+ else
+ {
+ newstr[k++]=c;
+ i++;
+ }
+ }
+
+ newstr[k] = '\0';
+ return newstr;
+}
+
+static int setIMStatus(char *id, _Vmap *map, _ConfigEntry *info,
+ char *returnedBUF, int size, Slapi_ValueSet **results)
+{
+ int status = PRESENCE_SUCCESS;
+ char *ontxtmap = NULL;
+ char *offtxtmap = NULL;
+ Slapi_Value *value = NULL;
+ Slapi_Value *value1 = NULL;
+ Slapi_Value *value2 = NULL;
+ struct berval bval;
+ Slapi_Attr *attr = NULL;
+ const struct berval *tmp = NULL;
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "--> setIMStatus \n", 0,0,0);
+ /**
+ * we got some data back so lets try to map it to
+ * the existing set of on/off data
+ *
+ * first we need to take a look at the
+ * returned type and depending upon that parse
+ * the data
+ */
+
+ if (map->syntax == PRESENCE_STRING) {
+ /**
+ * we had send a request for text
+ * but chances are we might end up
+ * getting an image back. So we need
+ * to compare it to existing set of
+ * images that we have in store ???
+ */
+ if (!strcasecmp(info->textReturnType, PRESENCE_TEXT_RETURN_TYPE)) {
+ /* return value is in text format */
+ ontxtmap = replaceIdWithValue(info->onTextMap, map->imID, id);
+ offtxtmap = replaceIdWithValue(info->offTextMap, map->imID, id);
+ if (!strcasecmp(ontxtmap, returnedBUF)) {
+ /**
+ * set the on value
+ */
+ value = slapi_value_new_string(PRESENCE_RETURNED_ON_TEXT);
+ } else if (!strcasecmp(offtxtmap, returnedBUF)) {
+ /**
+ * set the off value
+ */
+ value = slapi_value_new_string(PRESENCE_RETURNED_OFF_TEXT);
+ } else {
+ value = slapi_value_new_string(PRESENCE_RETURNED_ERROR_TEXT);
+ }
+ } else if (!strcasecmp(info->textReturnType, PRESENCE_BINARY_RETURN_TYPE)) {
+ /**
+ * call binary compare method
+ */
+ bval.bv_len = size;
+ bval.bv_val = returnedBUF;
+ value1 = slapi_value_new_berval(&bval);
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> returned size [%d] \n", bval.bv_len,0,0);
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> returned value [%s] \n", bval.bv_val,0,0);
+
+ attr = info->onGraphicMap;
+ if (attr) {
+ slapi_attr_first_value(attr, &value2);
+ tmp = slapi_value_get_berval(value2);
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Stored size [%d] \n", tmp->bv_len,0,0);
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Stored value [%s] \n", tmp->bv_val,0,0);
+ if (!slapi_value_compare(attr, value1, value2)) {
+ value = slapi_value_new_string(PRESENCE_RETURNED_ON_TEXT);
+ }
+ }
+ if (!value) {
+ attr = info->offGraphicMap;
+ if (attr) {
+ slapi_attr_first_value(attr, &value2);
+ if (!slapi_value_compare(attr, value1, value2)) {
+ value = slapi_value_new_string(PRESENCE_RETURNED_OFF_TEXT);
+ }
+ }
+ }
+ if (!value) {
+ attr = info->disabledGraphicMap;
+ if (attr) {
+ slapi_attr_first_value(attr, &value2);
+ if (!slapi_value_compare(attr, value1, value2)) {
+ value = slapi_value_new_string(PRESENCE_RETURNED_OFF_TEXT);
+ }
+ }
+ }
+ if (!value) {
+ /* some error */
+ value = slapi_value_new_string(PRESENCE_RETURNED_ERROR_TEXT);
+ }
+ } else {
+ /**
+ * set the error condition
+ */
+ value = slapi_value_new_string(PRESENCE_RETURNED_ERROR_TEXT);
+ }
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> value [%s] \n", returnedBUF,0,0);
+ } else {
+ /**
+ * we had send a request for image
+ * so whatever we get back we just
+ * return instead of analyzing it
+ */
+ if (!strcasecmp(info->graphicReturnType, PRESENCE_TEXT_RETURN_TYPE)) {
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> value [%s] \n", returnedBUF,0,0);
+ if (!strcasecmp(info->requestMethod, PRESENCE_REQUEST_METHOD_REDIRECT)) {
+ /**
+ * a redirect case in which we should probably have a
+ * gif in store so return that value
+ *
+ * for now
+ */
+
+ ontxtmap = replaceIdWithValue(info->onTextMap, map->imID, id);
+ offtxtmap = replaceIdWithValue(info->offTextMap, map->imID, id);
+ if (!strcasecmp(ontxtmap, returnedBUF)) {
+ /**
+ * set the on value
+ */
+ attr = info->onGraphicMap;
+ } else if (!strcasecmp(offtxtmap, returnedBUF)) {
+ /**
+ * set the off value
+ */
+ attr = info->offGraphicMap;
+ } else {
+ attr = info->disabledGraphicMap;
+ }
+ if (attr) {
+ slapi_attr_first_value(attr, &value);
+ }
+ } else {
+ /**
+ * for now just set the returned value
+ * should not happen in our case
+ * ERROR
+ */
+ }
+ } else {
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> value [%s] \n", returnedBUF,0,0);
+ bval.bv_len = size;
+ bval.bv_val = returnedBUF;
+ value = slapi_value_new_berval(&bval);
+ }
+ }
+ if (!*results) {
+ *results = slapi_valueset_new();
+ }
+
+ slapi_valueset_add_value(*results, value);
+
+ if (ontxtmap) {
+ slapi_ch_free((void **)&ontxtmap);
+ }
+ if (offtxtmap) {
+ slapi_ch_free((void **)&offtxtmap);
+ }
+ if (value && map->syntax == PRESENCE_STRING) {
+ slapi_value_free(&value);
+ }
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- setIMStatus \n", 0,0,0);
+
+ return status;
+}
+
+static int setTypes(PLHashEntry *he, PRIntn i, void *arg)
+{
+ int status;
+ int props = SLAPI_ATTR_FLAG_OPATTR;
+ Slapi_Attr *attr = NULL;
+ Slapi_ValueSet *results = NULL;
+ int type_name_disposition = 0;
+ char *actual_type_name = 0;
+ int free_flags = 0;
+
+ struct _vattrtypes *args = arg;
+ char *type = (char *)he->key;
+ _Vmap *map = (_Vmap *)he->value;
+ char *id = map->imID;
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "--> setTypes \n", 0,0,0);
+
+ status = slapi_vattr_values_get_sp(NULL, args->entry, id, &results, &type_name_disposition, &actual_type_name, 0, &free_flags);
+ if(status == PRESENCE_SUCCESS)
+ {
+ /* entry contains this attr */
+ vattr_type_thang thang = {0};
+
+ thang.type_name = type;
+ thang.type_flags = props;
+
+ slapi_vattrspi_add_type(args->context,&thang,0);
+
+ slapi_vattr_values_free(&results, &actual_type_name, free_flags);
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> ID [%s] Type[%s]\n", actual_type_name,type,0);
+ }
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- setTypes \n", 0,0,0);
+
+ return HT_ENUMERATE_NEXT;
+}
+
+
+static void
+logGraphicAttributeValue( Slapi_Attr *attr, const char *attrname )
+{
+ Slapi_Value *val = NULL;
+ const struct berval *v = NULL;
+
+ if ( LDAPDebugLevelIsSet( LDAP_DEBUG_PLUGIN )) {
+ slapi_attr_first_value(attr, &val);
+ v = slapi_value_get_berval(val);
+ if (v) {
+ char *ldifvalue;
+ size_t attrnamelen = strlen( attrname );
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> %s size [%d] \n",
+ attrname,v->bv_len,0);
+
+ ldifvalue = ldif_type_and_value_with_options(
+ (char *)attrname, /* XXX: had to cast away const */
+ v->bv_val, v->bv_len, 0 );
+ if ( NULL != ldifvalue ) {
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> %s value [\n%s]\n",
+ attrname,ldifvalue,0);
+ slapi_ch_free_string( &ldifvalue );
+ }
+ }
+ }
+}
+
+
+static void deleteMapTables()
+{
+ PL_HashTableEnumerateEntries(_IdConfigMapTable, destroyHashEntry, 0);
+ if (_IdConfigMapTable)
+ {
+ PL_HashTableDestroy(_IdConfigMapTable);
+ }
+
+ PL_HashTableEnumerateEntries(_IdVattrMapTable, destroyHashEntry, 0);
+ if (_IdVattrMapTable)
+ {
+ PL_HashTableDestroy(_IdVattrMapTable);
+ }
+ return;
+}
+
+static PRIntn destroyHashEntry(PLHashEntry *he, PRIntn index, void *arg)
+{
+ void *value = NULL;
+ if (he == NULL)
+ {
+ return HT_ENUMERATE_NEXT;
+ }
+ value = he->value;
+ if (value)
+ {
+ slapi_ch_free(&value);
+ }
+ return HT_ENUMERATE_REMOVE;
+}
+
+static void toLowerCase(char* str)
+{
+ if (str) {
+ char* lstr = str;
+ for(; (*lstr != '\0'); ++lstr) {
+ *lstr = tolower(*lstr);
+ }
+ }
+}
+
+
+/**
+ * utility function to print the array
+ */
+void printMapTable()
+{
+ PL_HashTableEnumerateEntries(_IdVattrMapTable, printIdVattrMapTable, 0);
+ PL_HashTableEnumerateEntries(_IdConfigMapTable, printIdConfigMapTable, 0);
+}
+
+PRIntn printIdVattrMapTable(PLHashEntry *he, PRIntn i, void *arg)
+{
+ char *key = (char *)he->key;
+ _Vmap *map = (_Vmap *)he->value;
+ printf("<---- Key -------> %s\n", key);
+ printf("<---- ImId ------> %s\n", map->imID);
+ printf("<---- syntax ----> %d\n", map->syntax);
+ return HT_ENUMERATE_NEXT;
+}
+
+PRIntn printIdConfigMapTable(PLHashEntry *he, PRIntn i, void *arg)
+{
+ char *key = (char *)he->key;
+ _ConfigEntry *value = (_ConfigEntry *)he->value;
+ printf("<- Key ---------------------> %s\n", key);
+ printf("<---- text_url -------------> %s\n", value->textURL);
+ printf("<---- graphic_url ----------> %s\n", value->graphicURL);
+ printf("<---- on_text_map ----------> %s\n", value->onTextMap);
+ printf("<---- off_text_map ---------> %s\n", value->offTextMap);
+ printf("<---- request_method -------> %s\n", value->requestMethod);
+ printf("<---- text_return_type -----> %s\n", value->textReturnType);
+ printf("<---- graphic_return_type --> %s\n", value->graphicReturnType);
+
+ return HT_ENUMERATE_NEXT;
+}
+
diff --git a/ldap/servers/plugins/presence/presence.def b/ldap/servers/plugins/presence/presence.def
new file mode 100644
index 00000000..4d083d26
--- /dev/null
+++ b/ldap/servers/plugins/presence/presence.def
@@ -0,0 +1,11 @@
+; BEGIN COPYRIGHT BLOCK
+; Copyright 2001 Sun Microsystems, Inc.
+; Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+; All rights reserved.
+; END COPYRIGHT BLOCK
+;
+DESCRIPTION 'Netscape Directory Server 6.2.1 Presence Plugin'
+EXPORTS
+ presence_init @2
+ plugin_init_debug_level @3
+ presence_version @4
diff --git a/ldap/servers/plugins/presence/presence.ldif b/ldap/servers/plugins/presence/presence.ldif
new file mode 100644
index 00000000..67bb977b
--- /dev/null
+++ b/ldap/servers/plugins/presence/presence.ldif
@@ -0,0 +1,44 @@
+dn:cn=ICQ Presence,cn=Presence,cn=plugins,cn=config
+changeType:modify
+replace:nsim-onvaluemapgraphic
+nsim-onvaluemapgraphic: D:/dev/ds60cvs/ldapserver/ldap/servers/plugins/presence/icq-online.gif
+
+dn:cn=ICQ Presence,cn=Presence,cn=plugins,cn=config
+changeType:modify
+replace:nsim-offvaluemapgraphic
+nsim-offvaluemapgraphic: D:/dev/ds60cvs/ldapserver/ldap/servers/plugins/presence/icq-offline.gif
+
+dn:cn=ICQ Presence,cn=Presence,cn=plugins,cn=config
+changeType:modify
+replace:nsim-disabledvaluemapgraphic
+nsim-disabledvaluemapgraphic: D:/dev/ds60cvs/ldapserver/ldap/servers/plugins/presence/icq-disabled.gif
+
+dn:cn=AIM Presence,cn=Presence,cn=plugins,cn=config
+changeType:modify
+replace:nsim-onvaluemapgraphic
+nsim-onvaluemapgraphic: D:/dev/ds60cvs/ldapserver/ldap/servers/plugins/presence/aim-online.gif
+
+dn:cn=AIM Presence,cn=Presence,cn=plugins,cn=config
+changeType:modify
+replace:nsim-offvaluemapgraphic
+nsim-offvaluemapgraphic: D:/dev/ds60cvs/ldapserver/ldap/servers/plugins/presence/aim-offline.gif
+
+dn:cn=ICQ Presence,cn=Presence,cn=plugins,cn=config
+changeType:modify
+replace:nsim-disabledvaluemapgraphic
+nsim-disabledvaluemapgraphic: D:/dev/ds60cvs/ldapserver/ldap/servers/plugins/presence/aim-offline.gif
+
+dn:cn=Yahoo Presence,cn=Presence,cn=plugins,cn=config
+changeType:modify
+replace:nsim-offvaluemapgraphic
+nsim-offvaluemapgraphic: D:/dev/ds60cvs/ldapserver/ldap/servers/plugins/presence/yahoo-offline.gif
+
+dn:cn=Yahoo Presence,cn=Presence,cn=plugins,cn=config
+changeType:modify
+replace:nsim-onvaluemapgraphic
+nsim-onvaluemapgraphic: D:/dev/ds60cvs/ldapserver/ldap/servers/plugins/presence/yahoo-online.gif
+
+dn:cn=Yahoo Presence,cn=Presence,cn=plugins,cn=config
+changeType:modify
+replace:nsim-disabledvaluemapgraphic
+nsim-disabledvaluemapgraphic: D:/dev/ds60cvs/ldapserver/ldap/servers/plugins/presence/yahoo-offline.gif
diff --git a/ldap/servers/plugins/pwdstorage/Makefile b/ldap/servers/plugins/pwdstorage/Makefile
new file mode 100644
index 00000000..efad0788
--- /dev/null
+++ b/ldap/servers/plugins/pwdstorage/Makefile
@@ -0,0 +1,115 @@
+#
+# BEGIN COPYRIGHT BLOCK
+# Copyright 2001 Sun Microsystems, Inc.
+# Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+# All rights reserved.
+# END COPYRIGHT BLOCK
+#
+#
+# GNU Makefile for Directory Server password_storaged-plugin.so password storage scheme plugins
+#
+
+LDAP_SRC = ../../..
+MCOM_ROOT = ../../../../..
+
+NOSTDCLEAN=true # don't let nsconfig.mk define target clean
+NOSTDSTRIP=true # don't let nsconfig.mk define target strip
+NSPR20=true # probably should be defined somewhere else (not sure where)
+
+OBJDEST = $(OBJDIR)/lib/libpwdstorage
+LIBDIR = $(LIB_RELDIR)
+
+include $(MCOM_ROOT)/ldapserver/nsconfig.mk
+include $(LDAP_SRC)/nsldap.mk
+
+ifeq ($(ARCH), WINNT)
+DEF_FILE:=./libpwdstorage.def
+endif
+
+CFLAGS+=$(SLCFLAGS)
+
+INCLUDES += -I$(LDAP_SRC)/servers/slapd
+
+PWD_OBJS= \
+ pwd_init.o \
+ clear_pwd.o \
+ crypt_pwd.o \
+ ns-mta-md5_pwd.o \
+ sha_pwd.o \
+ ssha_pwd.o \
+ md5c.o
+
+
+OBJS = $(addprefix $(OBJDEST)/, $(PWD_OBJS))
+
+ifeq ($(ARCH), WINNT)
+LIBPWD_DLL_OBJ = $(addprefix $(OBJDEST)/, dllmain.o)
+endif
+
+LIBPWD = $(addprefix $(LIBDIR)/, $(PWD_DLL).$(DLL_SUFFIX))
+
+ifeq ($(ARCH), WINNT)
+EXTRA_LIBS_DEP += \
+ $(LIBSLAPD_DEP) \
+ $(LDAP_LIBUTIL_DEP) \
+ $(LDAP_COMMON_LIBS_DEP)
+EXTRA_LIBS_DEP += \
+ $(LDAPSDK_DEP) \
+ $(SECURITY_DEP)
+EXTRA_LIBS += \
+ $(LIBSLAPD) \
+ $(LDAP_SDK_LIBLDAP_DLL) \
+ $(LIBUTIL) \
+ $(NSPRLINK) \
+ $(LDAP_COMMON_LIBS) \
+ $(SECURITYLINK)
+endif
+ifeq ($(ARCH), AIX)
+EXTRA_LIBS_DEP += \
+ $(LIBSLAPD_DEP) \
+ $(LDAP_LIBUTIL_DEP) \
+ $(LDAP_COMMON_LIBS_DEP)
+EXTRA_LIBS_DEP += \
+ $(LDAPSDK_DEP) \
+ $(SECURITY_DEP)
+EXTRA_LIBS += \
+ $(LIBSLAPDLINK) \
+ $(LDAP_SDK_LIBLDAP_DLL) \
+ $(LIBUTIL) \
+ $(NSPRLINK) \
+ $(LDAP_COMMON_LIBS) \
+ $(SECURITYLINK)
+endif
+
+ifeq ($(ARCH), HPUX)
+EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAPSDK_DEP) $(NSPR_DEP) $(SECURITY_DEP)
+EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(SECURITYLINK) $(NSPRLINK) $(ICULINK)
+endif
+
+ifeq ($(ARCH), WINNT)
+DLL_LDFLAGS += -def:"./libpwdstorage.def"
+CFLAGS+= /WX
+endif # WINNT
+
+ifeq ($(ARCH), AIX)
+LD=ld
+endif
+
+clientSDK:
+
+all: $(OBJDEST) $(LIBDIR) $(LIBPWD)
+
+$(LIBPWD): $(OBJS) $(LIBPWD_DLL_OBJ) $(DEF_FILE)
+ $(LINK_DLL) $(LIBPWD_DLL_OBJ) $(EXTRA_LIBS)
+
+veryclean: clean
+
+clean:
+ $(RM) $(OBJS)
+ifeq ($(ARCH), WINNT)
+ $(RM) $(LIBPWD_DLL_OBJ)
+endif
+ $(RM) $(LIBPWD)
+
+$(OBJDEST):
+ $(MKDIR) $(OBJDEST)
diff --git a/ldap/servers/plugins/pwdstorage/clear_pwd.c b/ldap/servers/plugins/pwdstorage/clear_pwd.c
new file mode 100644
index 00000000..4b2a3ca5
--- /dev/null
+++ b/ldap/servers/plugins/pwdstorage/clear_pwd.c
@@ -0,0 +1,27 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/*
+ * slapd hashed password routines
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "pwdstorage.h"
+
+int
+clear_pw_cmp( char *userpwd, char *dbpwd )
+{
+ return( strcmp( userpwd, dbpwd ));
+}
+
+char *
+clear_pw_enc( char *pwd )
+{
+ return( slapi_ch_strdup( pwd ));
+}
diff --git a/ldap/servers/plugins/pwdstorage/crypt_pwd.c b/ldap/servers/plugins/pwdstorage/crypt_pwd.c
new file mode 100644
index 00000000..df179ef6
--- /dev/null
+++ b/ldap/servers/plugins/pwdstorage/crypt_pwd.c
@@ -0,0 +1,91 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/*
+ * slapd hashed password routines
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#ifdef _WIN32
+char *crypt(char *key, char *salt);
+#else
+#include <sys/socket.h>
+#if defined( hpux ) || defined ( AIX ) || defined (LINUX) || defined (OSF1)
+#define __USE_XOPEN /* linux */
+#include <unistd.h>
+#else /* hpux */
+#include <crypt.h>
+#endif /* hpux */
+#endif /* _WIN32 */
+
+#include "pwdstorage.h"
+
+static PRLock *cryptlock; /* Some implementations of crypt are not thread safe. ie. ours & Irix */
+
+/* characters used in crypt encoding */
+static unsigned char itoa64[] = /* 0 ... 63 => ascii - 64 */
+ "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+
+
+void
+crypt_init()
+{
+ cryptlock = PR_NewLock();
+}
+
+int
+crypt_pw_cmp( char *userpwd, char *dbpwd )
+{
+ int rc;
+ char *cp;
+ PR_Lock(cryptlock);
+ /* we use salt (first 2 chars) of encoded password in call to crypt() */
+ cp = crypt( userpwd, dbpwd );
+ if (cp) {
+ rc= strcmp( dbpwd, cp);
+ } else {
+ rc = -1;
+ }
+ PR_Unlock(cryptlock);
+ return rc;
+}
+
+char *
+crypt_pw_enc( char *pwd )
+{
+ char *cry, salt[3];
+ char *enc= NULL;
+ long v;
+ static unsigned int seed = 0;
+
+ if ( seed == 0)
+ {
+ seed = (unsigned int)slapi_rand();
+ }
+ v = slapi_rand_r(&seed);
+
+ salt[0] = itoa64[v & 0x3f];
+ v >>= 6;
+ salt[1] = itoa64[v & 0x3f];
+ salt[2] = '\0';
+
+ PR_Lock(cryptlock);
+ cry = crypt( pwd, salt );
+ if ( cry != NULL )
+ {
+ enc = slapi_ch_malloc( 3 + CRYPT_NAME_LEN + strlen( cry ));
+ if ( enc != NULL )
+ {
+ sprintf( enc, "%c%s%c%s", PWD_HASH_PREFIX_START, CRYPT_SCHEME_NAME, PWD_HASH_PREFIX_END, cry );
+ }
+ }
+ PR_Unlock(cryptlock);
+ return( enc );
+}
+
diff --git a/ldap/servers/plugins/pwdstorage/dllmain.c b/ldap/servers/plugins/pwdstorage/dllmain.c
new file mode 100644
index 00000000..71530805
--- /dev/null
+++ b/ldap/servers/plugins/pwdstorage/dllmain.c
@@ -0,0 +1,91 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+ /*
+ * Microsoft Windows specifics for LIBPWDSTORAGE DLL
+ */
+#include "ldap.h"
+
+
+#ifdef _WIN32
+/* Lifted from Q125688
+ * How to Port a 16-bit DLL to a Win32 DLL
+ * on the MSVC 4.0 CD
+ */
+BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved)
+{
+
+ switch (fdwReason)
+ {
+ case DLL_PROCESS_ATTACH:
+ /* Code from LibMain inserted here. Return TRUE to keep the
+ DLL loaded or return FALSE to fail loading the DLL.
+
+ You may have to modify the code in your original LibMain to
+ account for the fact that it may be called more than once.
+ You will get one DLL_PROCESS_ATTACH for each process that
+ loads the DLL. This is different from LibMain which gets
+ called only once when the DLL is loaded. The only time this
+ is critical is when you are using shared data sections.
+ If you are using shared data sections for statically
+ allocated data, you will need to be careful to initialize it
+ only once. Check your code carefully.
+
+ Certain one-time initializations may now need to be done for
+ each process that attaches. You may also not need code from
+ your original LibMain because the operating system may now
+ be doing it for you.
+ */
+ /*
+ * 16 bit code calls UnlockData()
+ * which is mapped to UnlockSegment in windows.h
+ * in 32 bit world UnlockData is not defined anywhere
+ * UnlockSegment is mapped to GlobalUnfix in winbase.h
+ * and the docs for both UnlockSegment and GlobalUnfix say
+ * ".. function is oboslete. Segments have no meaning
+ * in the 32-bit environment". So we do nothing here.
+ */
+
+ break;
+
+ case DLL_THREAD_ATTACH:
+ /* Called each time a thread is created in a process that has
+ already loaded (attached to) this DLL. Does not get called
+ for each thread that exists in the process before it loaded
+ the DLL.
+
+ Do thread-specific initialization here.
+ */
+ break;
+
+ case DLL_THREAD_DETACH:
+ /* Same as above, but called when a thread in the process
+ exits.
+
+ Do thread-specific cleanup here.
+ */
+ break;
+
+ case DLL_PROCESS_DETACH:
+ /* Code from _WEP inserted here. This code may (like the
+ LibMain) not be necessary. Check to make certain that the
+ operating system is not doing it for you.
+ */
+
+ break;
+ }
+ /* The return value is only used for DLL_PROCESS_ATTACH; all other
+ conditions are ignored. */
+ return TRUE; /* successful DLL_PROCESS_ATTACH */
+}
+#else
+int CALLBACK
+LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine )
+{
+ /*UnlockData( 0 );*/
+ return( 1 );
+}
+#endif
diff --git a/ldap/servers/plugins/pwdstorage/libpwdstorage.def b/ldap/servers/plugins/pwdstorage/libpwdstorage.def
new file mode 100644
index 00000000..e19305d5
--- /dev/null
+++ b/ldap/servers/plugins/pwdstorage/libpwdstorage.def
@@ -0,0 +1,24 @@
+; BEGIN COPYRIGHT BLOCK
+; Copyright 2001 Sun Microsystems, Inc.
+; Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+; All rights reserved.
+; END COPYRIGHT BLOCK
+;
+DESCRIPTION 'Netscape Directory Server 7 password storage scheme Plugin'
+EXPORTS
+ sha_pwd_storage_scheme_init @2
+ ssha_pwd_storage_scheme_init @3
+ crypt_pwd_storage_scheme_init @4
+ clear_pwd_storage_scheme_init @5
+ ns_mta_md5_pwd_storage_scheme_init @6
+ clear_pw_cmp @7
+ crypt_pw_cmp @8
+ ns_mta_md5_pw_cmp @9
+ sha1_pw_cmp @10
+ sha1_pw_enc @11
+ salted_sha1_pw_enc @12
+ crypt_pw_enc @13
+ clear_pw_enc @14
+ mta_MD5Init @15
+ mta_MD5Update @16
+ mta_MD5Final @17
diff --git a/ldap/servers/plugins/pwdstorage/md5.h b/ldap/servers/plugins/pwdstorage/md5.h
new file mode 100644
index 00000000..6f7ec036
--- /dev/null
+++ b/ldap/servers/plugins/pwdstorage/md5.h
@@ -0,0 +1,63 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/*
+ * MD5 algorithm used by Netscape Mail Server
+ */
+
+/* MD5 code taken from reference implementation published in RFC 1321 */
+
+#ifndef _RFC1321_MD5_H_
+#define _RFC1321_MD5_H_
+
+/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
+ rights reserved.
+
+ License to copy and use this software is granted provided that it
+ is identified as the "RSA Data Security, Inc. MD5 Message-Digest
+ Algorithm" in all material mentioning or referencing this software
+ or this function.
+
+ License is also granted to make and use derivative works provided
+ that such works are identified as "derived from the RSA Data
+ Security, Inc. MD5 Message-Digest Algorithm" in all material
+ mentioning or referencing the derived work.
+
+ RSA Data Security, Inc. makes no representations concerning either
+ the merchantability of this software or the suitability of this
+ software for any particular purpose. It is provided "as is"
+ without express or implied warranty of any kind.
+
+ These notices must be retained in any copies of any part of this
+ documentation and/or software.
+ */
+
+#include "nspr.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef unsigned char * POINTER;
+typedef PRUint16 UINT2;
+typedef PRUint32 UINT4;
+
+/* MD5 context. */
+typedef struct {
+ UINT4 state[4]; /* state (ABCD) */
+ UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */
+ unsigned char buffer[64]; /* input buffer */
+} mta_MD5_CTX;
+
+void mta_MD5Init (mta_MD5_CTX *);
+void mta_MD5Update (mta_MD5_CTX *, const unsigned char *, unsigned int);
+void mta_MD5Final (unsigned char [16], mta_MD5_CTX *);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* end of _RFC1321_MD5_H_ */
+
diff --git a/ldap/servers/plugins/pwdstorage/md5c.c b/ldap/servers/plugins/pwdstorage/md5c.c
new file mode 100644
index 00000000..d78b772c
--- /dev/null
+++ b/ldap/servers/plugins/pwdstorage/md5c.c
@@ -0,0 +1,337 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* MD5 code taken from reference implementation published in RFC 1321 */
+
+/* MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm */
+
+/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
+ rights reserved.
+
+ License to copy and use this software is granted provided that it
+ is identified as the "RSA Data Security, Inc. MD5 Message-Digest
+ Algorithm" in all material mentioning or referencing this software
+ or this function.
+
+ License is also granted to make and use derivative works provided
+ that such works are identified as "derived from the RSA Data
+ Security, Inc. MD5 Message-Digest Algorithm" in all material
+ mentioning or referencing the derived work.
+
+ RSA Data Security, Inc. makes no representations concerning either
+ the merchantability of this software or the suitability of this
+ software for any particular purpose. It is provided "as is"
+ without express or implied warranty of any kind.
+
+ These notices must be retained in any copies of any part of this
+ documentation and/or software.
+ */
+
+#include "md5.h"
+
+/* Constants for MD5Transform routine. */
+
+#define S11 7
+#define S12 12
+#define S13 17
+#define S14 22
+#define S21 5
+#define S22 9
+#define S23 14
+#define S24 20
+#define S31 4
+#define S32 11
+#define S33 16
+#define S34 23
+#define S41 6
+#define S42 10
+#define S43 15
+#define S44 21
+
+static void MD5Transform (UINT4 [4], const unsigned char [64]);
+static void Encode (unsigned char *, const UINT4 *, unsigned int);
+static void Decode (UINT4 *, const unsigned char *, unsigned int);
+static void MD5_memcpy (POINTER, const POINTER, unsigned int);
+static void MD5_memset (POINTER, int, unsigned int);
+
+static unsigned char PADDING[64] = {
+ 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+/* F, G, H and I are basic MD5 functions.
+ */
+#define F(x, y, z) (((x) & (y)) | ((~(x)) & (z)))
+#define G(x, y, z) (((x) & (z)) | ((y) & (~(z))))
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define I(x, y, z) ((y) ^ ((x) | (~(z))))
+
+/* ROTATE_LEFT rotates x left n bits.
+ */
+#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n))))
+
+/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4.
+Rotation is separate from addition to prevent recomputation.
+ */
+#define FF(a, b, c, d, x, s, ac) { \
+ (a) += F ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define GG(a, b, c, d, x, s, ac) { \
+ (a) += G ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define HH(a, b, c, d, x, s, ac) { \
+ (a) += H ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define II(a, b, c, d, x, s, ac) { \
+ (a) += I ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+
+/* MD5 initialization. Begins an MD5 operation, writing a new context.
+ */
+void mta_MD5Init (context)
+mta_MD5_CTX *context; /* context */
+{
+ context->count[0] = context->count[1] = 0;
+ /* Load magic initialization constants.
+*/
+ context->state[0] = 0x67452301;
+ context->state[1] = 0xefcdab89;
+ context->state[2] = 0x98badcfe;
+ context->state[3] = 0x10325476;
+}
+
+/* MD5 block update operation. Continues an MD5 message-digest
+ operation, processing another message block, and updating the
+ context.
+ */
+void mta_MD5Update (context, input, inputLen)
+mta_MD5_CTX *context; /* context */
+const unsigned char *input; /* input block */
+unsigned int inputLen; /* length of input block */
+{
+ unsigned int i, index, partLen;
+
+ /* Compute number of bytes mod 64 */
+ index = (unsigned int)((context->count[0] >> 3) & 0x3F);
+
+ /* Update number of bits */
+ if ((context->count[0] += ((UINT4)inputLen << 3))
+ < ((UINT4)inputLen << 3))
+ context->count[1]++;
+ context->count[1] += ((UINT4)inputLen >> 29);
+
+ partLen = 64 - index;
+
+ /* Transform as many times as possible.
+*/
+ if (inputLen >= partLen) {
+ MD5_memcpy
+ ((POINTER)&context->buffer[index], (POINTER)input, partLen);
+ MD5Transform (context->state, context->buffer);
+
+ for (i = partLen; i + 63 < inputLen; i += 64)
+ MD5Transform (context->state, &input[i]);
+
+ index = 0;
+ }
+ else
+ i = 0;
+
+ /* Buffer remaining input */
+ MD5_memcpy
+ ((POINTER)&context->buffer[index], (POINTER)&input[i],
+ inputLen-i);
+}
+
+/* MD5 finalization. Ends an MD5 message-digest operation, writing the
+ the message digest and zeroizing the context.
+ */
+void mta_MD5Final (digest, context)
+unsigned char digest[16]; /* message digest */
+mta_MD5_CTX *context; /* context */
+{
+ unsigned char bits[8];
+ unsigned int index, padLen;
+
+ /* Save number of bits */
+ Encode (bits, context->count, 8);
+
+ /* Pad out to 56 mod 64.
+*/
+ index = (unsigned int)((context->count[0] >> 3) & 0x3f);
+ padLen = (index < 56) ? (56 - index) : (120 - index);
+ mta_MD5Update (context, PADDING, padLen);
+
+ /* Append length (before padding) */
+ mta_MD5Update (context, bits, 8);
+
+ /* Store state in digest */
+ Encode (digest, context->state, 16);
+
+ /* Zeroize sensitive information.
+*/
+ MD5_memset ((POINTER)context, 0, sizeof (*context));
+}
+
+/* MD5 basic transformation. Transforms state based on block.
+ */
+static void MD5Transform (state, block)
+UINT4 state[4];
+const unsigned char block[64];
+{
+ UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16];
+
+ Decode (x, block, 64);
+
+ /* Round 1 */
+ FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */
+ FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */
+ FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */
+ FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */
+ FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */
+ FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */
+ FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */
+ FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */
+ FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */
+ FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */
+ FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */
+ FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */
+ FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */
+ FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */
+ FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */
+ FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */
+
+ /* Round 2 */
+ GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */
+ GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */
+ GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */
+ GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */
+ GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */
+ GG (d, a, b, c, x[10], S22, 0x2441453); /* 22 */
+ GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */
+ GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */
+ GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */
+ GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */
+ GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */
+ GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */
+ GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */
+ GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */
+ GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */
+ GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */
+
+ /* Round 3 */
+ HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */
+ HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */
+ HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */
+ HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */
+ HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */
+ HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */
+ HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */
+ HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */
+ HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */
+ HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */
+ HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */
+ HH (b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */
+ HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */
+ HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */
+ HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */
+ HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */
+
+ /* Round 4 */
+ II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */
+ II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */
+ II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */
+ II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */
+ II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */
+ II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */
+ II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */
+ II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */
+ II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */
+ II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */
+ II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */
+ II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */
+ II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */
+ II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */
+ II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */
+ II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */
+
+ state[0] += a;
+ state[1] += b;
+ state[2] += c;
+ state[3] += d;
+
+ /* Zeroize sensitive information.
+*/
+ MD5_memset ((POINTER)x, 0, sizeof (x));
+}
+
+/* Encodes input (UINT4) into output (unsigned char). Assumes len is
+ a multiple of 4.
+ */
+static void Encode (output, input, len)
+unsigned char *output;
+const UINT4 *input;
+unsigned int len;
+{
+ unsigned int i, j;
+
+ for (i = 0, j = 0; j < len; i++, j += 4) {
+ output[j] = (unsigned char)(input[i] & 0xff);
+ output[j+1] = (unsigned char)((input[i] >> 8) & 0xff);
+ output[j+2] = (unsigned char)((input[i] >> 16) & 0xff);
+ output[j+3] = (unsigned char)((input[i] >> 24) & 0xff);
+ }
+}
+
+/* Decodes input (unsigned char) into output (UINT4). Assumes len is
+ a multiple of 4.
+ */
+static void Decode (output, input, len)
+UINT4 *output;
+const unsigned char *input;
+unsigned int len;
+{
+ unsigned int i, j;
+
+ for (i = 0, j = 0; j < len; i++, j += 4)
+ output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) |
+ (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24);
+}
+
+/* Note: Replace "for loop" with standard memcpy if possible.
+ */
+
+static void MD5_memcpy (output, input, len)
+POINTER output;
+const POINTER input;
+unsigned int len;
+{
+ unsigned int i;
+
+ for (i = 0; i < len; i++)
+ output[i] = input[i];
+}
+
+/* Note: Replace "for loop" with standard memset if possible.
+ */
+static void MD5_memset (output, value, len)
+POINTER output;
+int value;
+unsigned int len;
+{
+ unsigned int i;
+
+ for (i = 0; i < len; i++)
+ ((char *)output)[i] = (char)value;
+}
diff --git a/ldap/servers/plugins/pwdstorage/ns-mta-md5_pwd.bu b/ldap/servers/plugins/pwdstorage/ns-mta-md5_pwd.bu
new file mode 100644
index 00000000..7cdd74b3
--- /dev/null
+++ b/ldap/servers/plugins/pwdstorage/ns-mta-md5_pwd.bu
@@ -0,0 +1,405 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/*
+ * slapd hashed password routines
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "pwd.h"
+
+
+/*
+ * Netscape Mail Server MD5 support (compare-only; no support for encoding)
+ */
+
+static char * ns_mta_hextab = "0123456789abcdef";
+
+static void
+ns_mta_hexify(char *buffer, char *str, int len)
+{
+ char *pch = str;
+ char ch;
+ int i;
+
+ for(i = 0;i < len; i ++) {
+ ch = pch[i];
+ buffer[2*i] = ns_mta_hextab[(ch>>4)&15];
+ buffer[2*i+1] = ns_mta_hextab[ch&15];
+ }
+
+ return;
+}
+
+static char *
+ns_mta_hash_alg(char *buffer, char *salt, char *passwd)
+{
+ mta_MD5_CTX context;
+ char saltstr[2048];
+ unsigned char digest[16];
+
+ sprintf(saltstr,"%s%c%s%c%s",salt,89,passwd,247,salt);
+
+ mta_MD5Init(&context);
+ mta_MD5Update(&context,(unsigned char *)saltstr,strlen(saltstr));
+ mta_MD5Final(digest,&context);
+ ns_mta_hexify(buffer,(char*)digest,16);
+ buffer[32] = '\0';
+ return(buffer);
+
+}
+
+int
+ns_mta_md5_pw_cmp(char * clear, char *mangled)
+{
+ char mta_hash[33];
+ char mta_salt[33];
+ char buffer[65];
+
+ strncpy(mta_hash,mangled,32);
+ strncpy(mta_salt,&mangled[32],32);
+
+ mta_hash[32] = mta_salt[32] = 0;
+
+ return( strcmp(mta_hash,ns_mta_hash_alg(buffer,mta_salt,clear)));
+}
+
+
+/* MD5 code taken from reference implementation published in RFC 1321 */
+
+/* MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm */
+
+/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
+ rights reserved.
+
+ License to copy and use this software is granted provided that it
+ is identified as the "RSA Data Security, Inc. MD5 Message-Digest
+ Algorithm" in all material mentioning or referencing this software
+ or this function.
+
+ License is also granted to make and use derivative works provided
+ that such works are identified as "derived from the RSA Data
+ Security, Inc. MD5 Message-Digest Algorithm" in all material
+ mentioning or referencing the derived work.
+
+ RSA Data Security, Inc. makes no representations concerning either
+ the merchantability of this software or the suitability of this
+ software for any particular purpose. It is provided "as is"
+ without express or implied warranty of any kind.
+
+ These notices must be retained in any copies of any part of this
+ documentation and/or software.
+ */
+
+#include "pw.h"
+
+/* Constants for MD5Transform routine. */
+
+#define S11 7
+#define S12 12
+#define S13 17
+#define S14 22
+#define S21 5
+#define S22 9
+#define S23 14
+#define S24 20
+#define S31 4
+#define S32 11
+#define S33 16
+#define S34 23
+#define S41 6
+#define S42 10
+#define S43 15
+#define S44 21
+
+static void MD5Transform (UINT4 [4], const unsigned char [64]);
+static void Encode (unsigned char *, const UINT4 *, unsigned int);
+static void Decode (UINT4 *, const unsigned char *, unsigned int);
+static void MD5_memcpy (POINTER, const POINTER, unsigned int);
+static void MD5_memset (POINTER, int, unsigned int);
+
+static unsigned char PADDING[64] = {
+ 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+/* F, G, H and I are basic MD5 functions.
+ */
+#define F(x, y, z) (((x) & (y)) | ((~(x)) & (z)))
+#define G(x, y, z) (((x) & (z)) | ((y) & (~(z))))
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define I(x, y, z) ((y) ^ ((x) | (~(z))))
+
+/* ROTATE_LEFT rotates x left n bits.
+ */
+#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n))))
+
+/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4.
+Rotation is separate from addition to prevent recomputation.
+ */
+#define FF(a, b, c, d, x, s, ac) { \
+ (a) += F ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define GG(a, b, c, d, x, s, ac) { \
+ (a) += G ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define HH(a, b, c, d, x, s, ac) { \
+ (a) += H ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define II(a, b, c, d, x, s, ac) { \
+ (a) += I ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+
+/* MD5 initialization. Begins an MD5 operation, writing a new context.
+ */
+void mta_MD5Init (context)
+mta_MD5_CTX *context; /* context */
+{
+ context->count[0] = context->count[1] = 0;
+ /* Load magic initialization constants.
+*/
+ context->state[0] = 0x67452301;
+ context->state[1] = 0xefcdab89;
+ context->state[2] = 0x98badcfe;
+ context->state[3] = 0x10325476;
+}
+
+/* MD5 block update operation. Continues an MD5 message-digest
+ operation, processing another message block, and updating the
+ context.
+ */
+void mta_MD5Update (context, input, inputLen)
+mta_MD5_CTX *context; /* context */
+const unsigned char *input; /* input block */
+unsigned int inputLen; /* length of input block */
+{
+ unsigned int i, index, partLen;
+
+ /* Compute number of bytes mod 64 */
+ index = (unsigned int)((context->count[0] >> 3) & 0x3F);
+
+ /* Update number of bits */
+ if ((context->count[0] += ((UINT4)inputLen << 3))
+ < ((UINT4)inputLen << 3))
+ context->count[1]++;
+ context->count[1] += ((UINT4)inputLen >> 29);
+
+ partLen = 64 - index;
+
+ /* Transform as many times as possible.
+*/
+ if (inputLen >= partLen) {
+ MD5_memcpy
+ ((POINTER)&context->buffer[index], (POINTER)input, partLen);
+ MD5Transform (context->state, context->buffer);
+
+ for (i = partLen; i + 63 < inputLen; i += 64)
+ MD5Transform (context->state, &input[i]);
+
+ index = 0;
+ }
+ else
+ i = 0;
+
+ /* Buffer remaining input */
+ MD5_memcpy
+ ((POINTER)&context->buffer[index], (POINTER)&input[i],
+ inputLen-i);
+}
+
+/* MD5 finalization. Ends an MD5 message-digest operation, writing the
+ the message digest and zeroizing the context.
+ */
+void mta_MD5Final (digest, context)
+unsigned char digest[16]; /* message digest */
+mta_MD5_CTX *context; /* context */
+{
+ unsigned char bits[8];
+ unsigned int index, padLen;
+
+ /* Save number of bits */
+ Encode (bits, context->count, 8);
+
+ /* Pad out to 56 mod 64.
+*/
+ index = (unsigned int)((context->count[0] >> 3) & 0x3f);
+ padLen = (index < 56) ? (56 - index) : (120 - index);
+ mta_MD5Update (context, PADDING, padLen);
+
+ /* Append length (before padding) */
+ mta_MD5Update (context, bits, 8);
+
+ /* Store state in digest */
+ Encode (digest, context->state, 16);
+
+ /* Zeroize sensitive information.
+*/
+ MD5_memset ((POINTER)context, 0, sizeof (*context));
+}
+
+/* MD5 basic transformation. Transforms state based on block.
+ */
+static void MD5Transform (state, block)
+UINT4 state[4];
+const unsigned char block[64];
+{
+ UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16];
+
+ Decode (x, block, 64);
+
+ /* Round 1 */
+ FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */
+ FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */
+ FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */
+ FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */
+ FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */
+ FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */
+ FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */
+ FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */
+ FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */
+ FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */
+ FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */
+ FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */
+ FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */
+ FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */
+ FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */
+ FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */
+
+ /* Round 2 */
+ GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */
+ GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */
+ GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */
+ GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */
+ GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */
+ GG (d, a, b, c, x[10], S22, 0x2441453); /* 22 */
+ GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */
+ GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */
+ GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */
+ GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */
+ GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */
+ GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */
+ GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */
+ GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */
+ GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */
+ GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */
+
+ /* Round 3 */
+ HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */
+ HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */
+ HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */
+ HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */
+ HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */
+ HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */
+ HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */
+ HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */
+ HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */
+ HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */
+ HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */
+ HH (b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */
+ HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */
+ HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */
+ HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */
+ HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */
+
+ /* Round 4 */
+ II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */
+ II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */
+ II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */
+ II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */
+ II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */
+ II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */
+ II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */
+ II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */
+ II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */
+ II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */
+ II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */
+ II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */
+ II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */
+ II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */
+ II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */
+ II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */
+
+ state[0] += a;
+ state[1] += b;
+ state[2] += c;
+ state[3] += d;
+
+ /* Zeroize sensitive information.
+*/
+ MD5_memset ((POINTER)x, 0, sizeof (x));
+}
+
+/* Encodes input (UINT4) into output (unsigned char). Assumes len is
+ a multiple of 4.
+ */
+static void Encode (output, input, len)
+unsigned char *output;
+const UINT4 *input;
+unsigned int len;
+{
+ unsigned int i, j;
+
+ for (i = 0, j = 0; j < len; i++, j += 4) {
+ output[j] = (unsigned char)(input[i] & 0xff);
+ output[j+1] = (unsigned char)((input[i] >> 8) & 0xff);
+ output[j+2] = (unsigned char)((input[i] >> 16) & 0xff);
+ output[j+3] = (unsigned char)((input[i] >> 24) & 0xff);
+ }
+}
+
+/* Decodes input (unsigned char) into output (UINT4). Assumes len is
+ a multiple of 4.
+ */
+static void Decode (output, input, len)
+UINT4 *output;
+const unsigned char *input;
+unsigned int len;
+{
+ unsigned int i, j;
+
+ for (i = 0, j = 0; j < len; i++, j += 4)
+ output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) |
+ (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24);
+}
+
+/* Note: Replace "for loop" with standard memcpy if possible.
+ */
+
+static void MD5_memcpy (output, input, len)
+POINTER output;
+const POINTER input;
+unsigned int len;
+{
+ unsigned int i;
+
+ for (i = 0; i < len; i++)
+ output[i] = input[i];
+}
+
+/* Note: Replace "for loop" with standard memset if possible.
+ */
+static void MD5_memset (output, value, len)
+POINTER output;
+int value;
+unsigned int len;
+{
+ unsigned int i;
+
+ for (i = 0; i < len; i++)
+ ((char *)output)[i] = (char)value;
+}
+
diff --git a/ldap/servers/plugins/pwdstorage/ns-mta-md5_pwd.c b/ldap/servers/plugins/pwdstorage/ns-mta-md5_pwd.c
new file mode 100644
index 00000000..f3c11a10
--- /dev/null
+++ b/ldap/servers/plugins/pwdstorage/ns-mta-md5_pwd.c
@@ -0,0 +1,81 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/*
+ * slapd hashed password routines
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "pwdstorage.h"
+
+#include "md5.h" /* JCM - This is a core server header... These functions could be made part of the slapi API. */
+
+
+/*
+ * Netscape Mail Server MD5 support (compare-only; no support for encoding)
+ */
+
+static char * ns_mta_hextab = "0123456789abcdef";
+
+static void
+ns_mta_hexify(char *buffer, char *str, int len)
+{
+ char *pch = str;
+ char ch;
+ int i;
+
+ for(i = 0;i < len; i ++) {
+ ch = pch[i];
+ buffer[2*i] = ns_mta_hextab[(ch>>4)&15];
+ buffer[2*i+1] = ns_mta_hextab[ch&15];
+ }
+
+ return;
+}
+
+static char *
+ns_mta_hash_alg(char *buffer, char *salt, char *passwd)
+{
+ mta_MD5_CTX context;
+ char *saltstr;
+ unsigned char digest[16];
+
+
+ if ( (saltstr = slapi_ch_malloc(strlen(salt)*2 + strlen(passwd) + 3))
+ == NULL ) {
+ return( NULL );
+ }
+
+ sprintf(saltstr,"%s%c%s%c%s",salt,89,passwd,247,salt);
+
+ mta_MD5Init(&context);
+ mta_MD5Update(&context,(unsigned char *)saltstr,strlen(saltstr));
+ mta_MD5Final(digest,&context);
+ ns_mta_hexify(buffer,(char*)digest,16);
+ buffer[32] = '\0';
+ slapi_ch_free((void**)&saltstr);
+ return(buffer);
+
+}
+
+int
+ns_mta_md5_pw_cmp(char * clear, char *mangled)
+{
+ char mta_hash[33];
+ char mta_salt[33];
+ char buffer[65];
+
+ strncpy(mta_hash,mangled,32);
+ strncpy(mta_salt,&mangled[32],32);
+
+ mta_hash[32] = mta_salt[32] = 0;
+
+ return( strcmp(mta_hash,ns_mta_hash_alg(buffer,mta_salt,clear)));
+}
+
diff --git a/ldap/servers/plugins/pwdstorage/pwd_init.c b/ldap/servers/plugins/pwdstorage/pwd_init.c
new file mode 100644
index 00000000..4ee5138b
--- /dev/null
+++ b/ldap/servers/plugins/pwdstorage/pwd_init.c
@@ -0,0 +1,146 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "pwdstorage.h"
+#include "dirver.h"
+
+static Slapi_PluginDesc sha_pdesc = { "sha-password-storage-scheme", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Secure Hashing Algorithm (SHA)" };
+
+static Slapi_PluginDesc ssha_pdesc = { "ssha-password-storage-scheme", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Salted Secure Hashing Algorithm (SSHA)" };
+
+static Slapi_PluginDesc crypt_pdesc = { "crypt-password-storage-scheme", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Unix crypt algorithm (CRYPT)" };
+
+static Slapi_PluginDesc clear_pdesc = { "clear-password-storage-scheme", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "No encryption (CLEAR)" };
+
+static Slapi_PluginDesc ns_mta_md5_pdesc = { "NS-MTA-MD5-password-storage-scheme", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Netscape MD5 (NS-MTA-MD5)" };
+
+static char *plugin_name = "NSPwdStoragePlugin";
+
+int
+sha_pwd_storage_scheme_init( Slapi_PBlock *pb )
+{
+ int rc;
+ char *name;
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, plugin_name, "=> sha_pwd_storage_scheme_init\n" );
+
+ rc = slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION,
+ (void *) SLAPI_PLUGIN_VERSION_01 );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION,
+ (void *)&sha_pdesc );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_ENC_FN,
+ (void *) sha1_pw_enc);
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_CMP_FN,
+ (void *) sha1_pw_cmp );
+ name = slapi_ch_strdup("SHA");
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_NAME,
+ name );
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, plugin_name, "<= sha_pwd_storage_scheme_init %d\n\n", rc );
+
+ return( rc );
+}
+
+int
+ssha_pwd_storage_scheme_init( Slapi_PBlock *pb )
+{
+ int rc;
+ char *name;
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, plugin_name, "=> ssha_pwd_storage_scheme_init\n" );
+
+ rc = slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION,
+ (void *) SLAPI_PLUGIN_VERSION_01 );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION,
+ (void *)&ssha_pdesc );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_ENC_FN,
+ (void *) salted_sha1_pw_enc );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_CMP_FN,
+ (void *) sha1_pw_cmp );
+ name = slapi_ch_strdup("SSHA");
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_NAME,
+ name );
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, plugin_name, "<= ssha_pwd_storage_scheme_init %d\n\n", rc );
+ return( rc );
+}
+
+int
+crypt_pwd_storage_scheme_init( Slapi_PBlock *pb )
+{
+ int rc;
+ char *name;
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, plugin_name, "=> crypt_pwd_storage_scheme_init\n" );
+
+ crypt_init();
+ rc = slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION,
+ (void *) SLAPI_PLUGIN_VERSION_01 );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION,
+ (void *)&crypt_pdesc );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_ENC_FN,
+ (void *) crypt_pw_enc );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_CMP_FN,
+ (void *) crypt_pw_cmp );
+ name = slapi_ch_strdup("CRYPT");
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_NAME,
+ name );
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, plugin_name, "<= crypt_pwd_storage_scheme_init %d\n\n", rc );
+ return( rc );
+}
+
+int
+clear_pwd_storage_scheme_init( Slapi_PBlock *pb )
+{
+ int rc;
+ char *name;
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, plugin_name, "=> clear_pwd_storage_scheme_init\n" );
+
+ rc = slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION,
+ (void *) SLAPI_PLUGIN_VERSION_01 );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION,
+ (void *)&clear_pdesc );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_ENC_FN,
+ (void *) clear_pw_enc );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_CMP_FN,
+ (void *) clear_pw_cmp );
+ name = slapi_ch_strdup("CLEAR");
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_NAME,
+ name );
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, plugin_name, "<= clear_pwd_storage_scheme_init %d\n\n", rc );
+ return( rc );
+}
+
+int
+ns_mta_md5_pwd_storage_scheme_init( Slapi_PBlock *pb )
+{
+ int rc;
+ char *name;
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, plugin_name, "=> ns_mta_md5_pwd_storage_scheme_init\n" );
+
+ rc = slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION,
+ (void *) SLAPI_PLUGIN_VERSION_01 );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION,
+ (void *)&ns_mta_md5_pdesc );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_ENC_FN,
+ (void *) NULL );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_CMP_FN,
+ (void *) ns_mta_md5_pw_cmp );
+ name = slapi_ch_strdup("NS-MTA-MD5");
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_NAME,
+ name );
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, plugin_name, "<= ns_mta_md5_pwd_storage_scheme_init %d\n\n", rc );
+ return( rc );
+}
diff --git a/ldap/servers/plugins/pwdstorage/pwdstorage.h b/ldap/servers/plugins/pwdstorage/pwdstorage.h
new file mode 100644
index 00000000..0e938cb9
--- /dev/null
+++ b/ldap/servers/plugins/pwdstorage/pwdstorage.h
@@ -0,0 +1,99 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#ifndef _PWDSTORAGE_H
+#define _PWDSTORAGE_H
+
+#include "slapi-plugin.h"
+#include <ssl.h>
+#include "nspr.h"
+#include "ldif.h"
+#include "md5.h"
+
+#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */
+
+#define PWD_HASH_PREFIX_START '{'
+#define PWD_HASH_PREFIX_END '}'
+
+#define SHA1_SCHEME_NAME "SHA"
+#define SHA1_NAME_LEN 3
+#define SALTED_SHA1_SCHEME_NAME "SSHA"
+#define SALTED_SHA1_NAME_LEN 4
+#define CRYPT_SCHEME_NAME "crypt"
+#define CRYPT_NAME_LEN 5
+#define NS_MTA_MD5_SCHEME_NAME "NS-MTA-MD5"
+#define NS_MTA_MD5_NAME_LEN 10
+#define CLEARTEXT_SCHEME_NAME "clear"
+#define CLEARTEXT_NAME_LEN 5
+
+SECStatus sha1_salted_hash(unsigned char *hash_out, char *pwd, struct berval *salt);
+int sha1_pw_cmp( char *userpwd, char *dbpwd );
+char * sha1_pw_enc( char *pwd );
+char * salted_sha1_pw_enc( char *pwd );
+int clear_pw_cmp( char *userpwd, char *dbpwd );
+char *clear_pw_enc( char *pwd );
+void crypt_init();
+int crypt_pw_cmp( char *userpwd, char *dbpwd );
+char *crypt_pw_enc( char *pwd );
+int ns_mta_md5_pw_cmp( char *userpwd, char *dbpwd );
+
+
+#if !defined(NET_SSL)
+/******************************************/
+/*
+ * Some of the stuff below depends on a definition for uint32, so
+ * we include one here. Other definitions appear in nspr/prtypes.h,
+ * at least. All the platforms we support use 32-bit ints.
+ */
+typedef unsigned int uint32;
+
+
+/******************************************/
+/*
+ * The following is from ds.h, which the libsec sec.h stuff depends on (see
+ * comment below).
+ */
+/*
+** A status code. Status's are used by procedures that return status
+** values. Again the motivation is so that a compiler can generate
+** warnings when return values are wrong. Correct testing of status codes:
+**
+** DSStatus rv;
+** rv = some_function (some_argument);
+** if (rv != DSSuccess)
+** do_an_error_thing();
+**
+*/
+typedef enum DSStatusEnum {
+ DSWouldBlock = -2,
+ DSFailure = -1,
+ DSSuccess = 0
+} DSStatus;
+
+
+/******************************************/
+/*
+ * All of the SHA1-related defines are from libsec's "sec.h" -- including
+ * it directly pulls in way too much stuff that we conflict with. Ugh.
+ */
+
+/*
+ * Number of bytes each hash algorithm produces
+ */
+#define SHA1_LENGTH 20
+
+/******************************************/
+/*
+** SHA-1 secure hash function
+*/
+
+/*
+** Hash a null terminated string "src" into "dest" using SHA-1
+*/
+DSStatus SHA1_Hash(unsigned char *dest, char *src);
+
+#endif /* !defined(NET_SSL) */
+
+#endif /* _PWDSTORAGE_H */
diff --git a/ldap/servers/plugins/pwdstorage/sha_pwd.c b/ldap/servers/plugins/pwdstorage/sha_pwd.c
new file mode 100644
index 00000000..c8cf435d
--- /dev/null
+++ b/ldap/servers/plugins/pwdstorage/sha_pwd.c
@@ -0,0 +1,111 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/*
+ * slapd hashed password routines
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "pwdstorage.h"
+
+#if defined(NET_SSL)
+#include <sechash.h>
+#endif /* NET_SSL */
+
+#define SHA1_SALT_LENGTH 8 /* number of bytes of data in salt */
+#define NOT_FIRST_TIME (time_t)1 /* not the first logon */
+
+static char *hasherrmsg = "pw_cmp: %s userPassword \"%s\" is the wrong length or is not properly encoded BASE64\n";
+
+static char *plugin_name = "NSPwdStoragePlugin";
+
+#define DS40B1_SALTED_SHA_LENGTH 18
+/* Directory Server 4.0 Beta 1 implemented a scheme that stored
+ * 8 bytes of salt plus the first 10 bytes of the SHA-1 digest.
+ * It's obsolescent now, but we still handle such stored values.
+ */
+
+int
+sha1_pw_cmp (char *userpwd, char *dbpwd )
+{
+ /*
+ * SHA1 passwords are stored in the database as SHA1_LENGTH bytes of
+ * hash, followed by zero or more bytes of salt, all BASE64 encoded.
+ */
+ int result = 1; /* failure */
+ unsigned char userhash[SHA1_LENGTH];
+ unsigned char quick_dbhash[SHA1_LENGTH + SHA1_SALT_LENGTH + 3];
+ unsigned char *dbhash = quick_dbhash;
+ struct berval salt;
+ int hash_len; /* must be a signed valued -- see below */
+
+ /*
+ * Decode hash stored in database.
+ *
+ * Note that ldif_base64_decode() returns a value less than zero to
+ * indicate that a decoding error occurred, so it is critical that
+ * hash_len be a signed value.
+ */
+ hash_len = (((strlen(dbpwd) + 3) / 4) * 3); /* maybe less */
+ if ( hash_len > sizeof(quick_dbhash) ) { /* get more space: */
+ dbhash = (unsigned char*) slapi_ch_malloc( hash_len );
+ if ( dbhash == NULL ) goto loser;
+ }
+ hash_len = ldif_base64_decode( dbpwd, dbhash );
+ if ( hash_len >= SHA1_LENGTH ) {
+ salt.bv_val = (void*)(dbhash + SHA1_LENGTH);
+ salt.bv_len = hash_len - SHA1_LENGTH;
+ } else if ( hash_len == DS40B1_SALTED_SHA_LENGTH ) {
+ salt.bv_val = (void*)dbhash;
+ salt.bv_len = 8;
+ } else { /* unsupported, invalid BASE64 (hash_len < 0), or similar */
+ slapi_log_error( SLAPI_LOG_PLUGIN, plugin_name, hasherrmsg, SHA1_SCHEME_NAME, dbpwd );
+ goto loser;
+ }
+
+ /* SHA1 hash the user's key */
+ if ( sha1_salted_hash( userhash, userpwd, &salt ) != SECSuccess ) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, plugin_name, "sha1_pw_cmp: SHA1_Hash() failed\n");
+ goto loser;
+ }
+
+ /* the proof is in the comparison... */
+ result = ( hash_len == DS40B1_SALTED_SHA_LENGTH ) ?
+ ( memcmp( userhash, dbhash + 8, hash_len - 8 )) :
+ ( memcmp( userhash, dbhash, SHA1_LENGTH ));
+
+ loser:
+ if ( dbhash && dbhash != quick_dbhash ) slapi_ch_free( (void**)&dbhash );
+ return result;
+}
+
+
+char *
+sha1_pw_enc( char *pwd )
+{
+ unsigned char hash[ SHA1_LENGTH ];
+ char *enc;
+
+ /* SHA1 hash the user's key */
+ if ( sha1_salted_hash( hash, pwd, NULL ) != SECSuccess ) {
+ return( NULL );
+ }
+
+ if (( enc = slapi_ch_malloc( 3 + SHA1_NAME_LEN +
+ LDIF_BASE64_LEN( SHA1_LENGTH ))) == NULL ) {
+ return( NULL );
+ }
+
+ sprintf( enc, "%c%s%c", PWD_HASH_PREFIX_START, SHA1_SCHEME_NAME,
+ PWD_HASH_PREFIX_END );
+ (void)ldif_base64_encode( hash, enc + 2 + SHA1_NAME_LEN,
+ SHA1_LENGTH, -1 );
+
+ return( enc );
+}
diff --git a/ldap/servers/plugins/pwdstorage/ssha_pwd.c b/ldap/servers/plugins/pwdstorage/ssha_pwd.c
new file mode 100644
index 00000000..b3c82d6d
--- /dev/null
+++ b/ldap/servers/plugins/pwdstorage/ssha_pwd.c
@@ -0,0 +1,112 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/*
+ * slapd hashed password routines
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "pwdstorage.h"
+#include "prtime.h"
+#include "prlong.h"
+
+#if defined(NET_SSL)
+#include <pk11func.h>
+#include <pk11pqg.h>
+#endif /* NET_SSL */
+
+#define SHA1_SALT_LENGTH 8 /* number of bytes of data in salt */
+
+static void ssha_rand_array(void *randx, size_t len);
+
+
+/* ***************************************************
+ Identical function to slapi_rand_array in util.c, but can't use
+ that here since this module is included in libds_admin, which doesn't
+ link to libslapd.
+ *************************************************** */
+static void
+ssha_rand_array(void *randx, size_t len)
+{
+ PK11_RandomUpdate(randx, len);
+ PK11_GenerateRandom((unsigned char *)randx, (int)len);
+}
+
+/*
+ * A salted SHA1 hash
+ * if salt is null, no salt is used (this is for backward compatibility)
+*/
+SECStatus
+sha1_salted_hash(unsigned char *hash_out, char *pwd, struct berval *salt)
+{
+ PK11Context *ctx;
+ unsigned int outLen;
+ SECStatus rc;
+
+ if (salt && salt->bv_len) {
+ ctx = PK11_CreateDigestContext(SEC_OID_SHA1);
+ if (ctx == NULL) {
+ rc = SECFailure;
+ }
+ else {
+ PK11_DigestBegin(ctx);
+ PK11_DigestOp(ctx, (unsigned char*)pwd, strlen(pwd));
+ PK11_DigestOp(ctx, (unsigned char*)(salt->bv_val), salt->bv_len);
+ PK11_DigestFinal(ctx, hash_out, &outLen, SHA1_LENGTH);
+ PK11_DestroyContext(ctx, 1);
+ if (outLen == SHA1_LENGTH)
+ rc = SECSuccess;
+ else
+ rc = SECFailure;
+ }
+ }
+ else {
+ /*backward compatibility*/
+ rc = PK11_HashBuf(SEC_OID_SHA1, hash_out, (unsigned char *)pwd, strlen(pwd));
+ }
+
+ return rc;
+}
+
+char *
+salted_sha1_pw_enc( char *pwd )
+{
+ unsigned char hash[ SHA1_LENGTH + SHA1_SALT_LENGTH ];
+ unsigned char *salt = hash + SHA1_LENGTH;
+ struct berval saltval;
+ char *enc;
+
+ saltval.bv_val = (void*)salt;
+ saltval.bv_len = SHA1_SALT_LENGTH;
+
+ /* generate a new random salt */
+ /* Note: the uninitialized salt array provides a little extra entropy
+ * to the random array generation, but it is not really needed since
+ * PK11_GenerateRandom takes care of seeding. In any case, it doesn't
+ * hurt. */
+ ssha_rand_array( salt, SHA1_SALT_LENGTH );
+
+ /* SHA1 hash the user's key */
+ if ( sha1_salted_hash( hash, pwd, &saltval ) != SECSuccess ) {
+ return( NULL );
+ }
+
+ if (( enc = PR_Malloc( 3 + SALTED_SHA1_NAME_LEN +
+ LDIF_BASE64_LEN(sizeof(hash)))) == NULL ) {
+ return( NULL );
+ }
+
+ sprintf( enc, "%c%s%c", PWD_HASH_PREFIX_START, SALTED_SHA1_SCHEME_NAME,
+ PWD_HASH_PREFIX_END );
+ (void)ldif_base64_encode( hash, enc + 2 + SALTED_SHA1_NAME_LEN,
+ sizeof(hash), -1 );
+
+ return( enc );
+}
+
diff --git a/ldap/servers/plugins/referint/Makefile b/ldap/servers/plugins/referint/Makefile
new file mode 100644
index 00000000..acf09c42
--- /dev/null
+++ b/ldap/servers/plugins/referint/Makefile
@@ -0,0 +1,72 @@
+#
+# BEGIN COPYRIGHT BLOCK
+# Copyright 2001 Sun Microsystems, Inc.
+# Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+# All rights reserved.
+# END COPYRIGHT BLOCK
+#
+LDAP_SRC = ../../..
+MCOM_ROOT = ../../../../..
+
+NOSTDCLEAN=true # don't let nsconfig.mk define target clean
+NOSTDSTRIP=true # don't let nsconfig.mk define target strip
+NSPR20=true # probably should be defined somewhere else (not sure where)
+
+OBJDEST = $(OBJDIR)/lib/referint-plugin
+LIBDIR = $(LIB_RELDIR)
+
+include $(MCOM_ROOT)/ldapserver/nsconfig.mk
+include $(LDAP_SRC)/nsldap.mk
+
+ifeq ($(ARCH), WINNT)
+DEF_FILE:=./referint.def
+endif
+
+REFERINT_OBJS = referint.o
+OBJS = $(addprefix $(OBJDEST)/, $(REFERINT_OBJS))
+
+INCLUDES += -I../../slapd -I../../../include
+CFLAGS+=$(SLCFLAGS) -DSLAPD_LOGGING
+
+EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAPLINK_DEP) $(NSPRLINK_DEP)
+EXTRA_LIBS += $(LIBSLAPD) $(LDAPLINK) $(NSPRLINK)
+
+ifeq ($(ARCH), HPUX)
+EXTRA_LIBS_DEP += $(SECURITY_DEP)
+EXTRA_LIBS += $(SECURITYLINK)
+endif
+
+ifeq ($(ARCH), WINNT)
+REFERINT_DLL_OBJ = $(addprefix $(OBJDEST)/, dllmain.o)
+endif
+
+ifeq ($(ARCH), AIX)
+LD=ld
+endif
+
+REFERINT= $(addprefix $(LIBDIR)/, $(REFERINT_DLL).$(DLL_SUFFIX))
+
+clientSDK:
+
+all: $(OBJDEST) $(LIBDIR) $(REFERINT)
+
+ifeq ($(ARCH), WINNT)
+$(REFERINT): $(OBJS) $(REFERINT_DLL_OBJ) $(DEF_FILE)
+ $(LINK_DLL) $(REFERINT_DLL_OBJ) $(EXTRA_LIBS) /DEF:$(DEF_FILE)
+else
+$(REFERINT): $(OBJS) $(REFERINT_DLL_OBJ)
+ $(LINK_DLL) $(REFERINT_DLL_OBJ) $(EXTRA_LIBS)
+endif
+
+
+veryclean: clean
+
+clean:
+ $(RM) $(OBJS)
+ifeq ($(ARCH), WINNT)
+ $(RM) $(REFERINT_DLL_OBJ)
+endif
+ $(RM) $(REFERINT)
+
+$(OBJDEST):
+ $(MKDIR) $(OBJDEST)
diff --git a/ldap/servers/plugins/referint/dllmain.c b/ldap/servers/plugins/referint/dllmain.c
new file mode 100644
index 00000000..96bfcbb0
--- /dev/null
+++ b/ldap/servers/plugins/referint/dllmain.c
@@ -0,0 +1,95 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/*
+ * Microsoft Windows specifics for BACK-LDBM DLL
+ */
+#include "ldap.h"
+
+
+#ifdef _WIN32
+/* Lifted from Q125688
+ * How to Port a 16-bit DLL to a Win32 DLL
+ * on the MSVC 4.0 CD
+ */
+BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved)
+{
+ WSADATA wsadata;
+
+ switch (fdwReason)
+ {
+ case DLL_PROCESS_ATTACH:
+ /* Code from LibMain inserted here. Return TRUE to keep the
+ DLL loaded or return FALSE to fail loading the DLL.
+
+ You may have to modify the code in your original LibMain to
+ account for the fact that it may be called more than once.
+ You will get one DLL_PROCESS_ATTACH for each process that
+ loads the DLL. This is different from LibMain which gets
+ called only once when the DLL is loaded. The only time this
+ is critical is when you are using shared data sections.
+ If you are using shared data sections for statically
+ allocated data, you will need to be careful to initialize it
+ only once. Check your code carefully.
+
+ Certain one-time initializations may now need to be done for
+ each process that attaches. You may also not need code from
+ your original LibMain because the operating system may now
+ be doing it for you.
+ */
+ /*
+ * 16 bit code calls UnlockData()
+ * which is mapped to UnlockSegment in windows.h
+ * in 32 bit world UnlockData is not defined anywhere
+ * UnlockSegment is mapped to GlobalUnfix in winbase.h
+ * and the docs for both UnlockSegment and GlobalUnfix say
+ * ".. function is oboslete. Segments have no meaning
+ * in the 32-bit environment". So we do nothing here.
+ */
+
+ if( errno = WSAStartup(0x0101, &wsadata ) != 0 )
+ return FALSE;
+
+ break;
+
+ case DLL_THREAD_ATTACH:
+ /* Called each time a thread is created in a process that has
+ already loaded (attached to) this DLL. Does not get called
+ for each thread that exists in the process before it loaded
+ the DLL.
+
+ Do thread-specific initialization here.
+ */
+ break;
+
+ case DLL_THREAD_DETACH:
+ /* Same as above, but called when a thread in the process
+ exits.
+
+ Do thread-specific cleanup here.
+ */
+ break;
+
+ case DLL_PROCESS_DETACH:
+ /* Code from _WEP inserted here. This code may (like the
+ LibMain) not be necessary. Check to make certain that the
+ operating system is not doing it for you.
+ */
+ WSACleanup();
+
+ break;
+ }
+ /* The return value is only used for DLL_PROCESS_ATTACH; all other
+ conditions are ignored. */
+ return TRUE; // successful DLL_PROCESS_ATTACH
+}
+#else
+int CALLBACK
+LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine )
+{
+ /*UnlockData( 0 );*/
+ return( 1 );
+}
+#endif
diff --git a/ldap/servers/plugins/referint/referint.c b/ldap/servers/plugins/referint/referint.c
new file mode 100644
index 00000000..e0ad15fd
--- /dev/null
+++ b/ldap/servers/plugins/referint/referint.c
@@ -0,0 +1,808 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include <stdio.h>
+#include <string.h>
+#include "portable.h"
+#include "slapi-plugin.h"
+#include "slap.h"
+#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */
+#include "dirver.h"
+
+/* include NSPR header files */
+#include "prthread.h"
+#include "prlock.h"
+#include "prerror.h"
+#include "prcvar.h"
+#include "prio.h"
+
+/* get file mode flags for unix */
+#ifndef _WIN32
+#include <sys/stat.h>
+#endif
+
+#define REFERINT_PLUGIN_SUBSYSTEM "referint-plugin" /* used for logging */
+
+#ifdef _WIN32
+#define REFERINT_DEFAULT_FILE_MODE 0
+#else
+#define REFERINT_DEFAULT_FILE_MODE S_IRUSR | S_IWUSR
+#endif
+
+
+#define MAX_LINE 2048
+#define READ_BUFSIZE 4096
+#define MY_EOF 0
+
+/* function prototypes */
+int referint_postop_init( Slapi_PBlock *pb );
+int referint_postop_del( Slapi_PBlock *pb );
+int referint_postop_modrdn( Slapi_PBlock *pb );
+int referint_postop_start( Slapi_PBlock *pb);
+int referint_postop_close( Slapi_PBlock *pb);
+int update_integrity(char **argv, char *origDN, char *newrDN, int logChanges);
+void referint_thread_func(void *arg);
+int GetNextLine(char *dest, int size_dest, PRFileDesc *stream);
+void writeintegritylog(char *logfilename, char *dn, char *newrdn);
+int my_fgetc(PRFileDesc *stream);
+
+/* global thread control stuff */
+
+static PRLock *referint_mutex = NULL;
+static PRThread *referint_tid = NULL;
+int keeprunning = 0;
+
+static PRLock *keeprunning_mutex = NULL;
+static PRCondVar *keeprunning_cv = NULL;
+
+static Slapi_PluginDesc pdesc = { "referint", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT,
+ "referential integrity plugin" };
+
+static void* referint_plugin_identity = NULL;
+
+#ifdef _WIN32
+int *module_ldap_debug = 0;
+
+void plugin_init_debug_level(int *level_ptr)
+{
+ module_ldap_debug = level_ptr;
+}
+#endif
+
+int
+referint_postop_init( Slapi_PBlock *pb )
+{
+
+ /*
+ * Get plugin identity and stored it for later use
+ * Used for internal operations
+ */
+
+ slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &referint_plugin_identity);
+ PR_ASSERT (referint_plugin_identity);
+
+ if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION,
+ SLAPI_PLUGIN_VERSION_01 ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION,
+ (void *)&pdesc ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_POST_DELETE_FN,
+ (void *) referint_postop_del ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_POST_MODRDN_FN,
+ (void *) referint_postop_modrdn ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN,
+ (void *) referint_postop_start ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN,
+ (void *) referint_postop_close ) != 0)
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM,
+ "referint_postop_init failed\n" );
+ return( -1 );
+ }
+
+ return( 0 );
+}
+
+
+int
+referint_postop_del( Slapi_PBlock *pb )
+{
+ char *dn;
+ int rc;
+ int oprc;
+ char **argv;
+ int argc;
+ int delay;
+ int logChanges=0;
+ int isrepop = 0;
+
+ if ( slapi_pblock_get( pb, SLAPI_IS_REPLICATED_OPERATION, &isrepop ) != 0 ||
+ slapi_pblock_get( pb, SLAPI_DELETE_TARGET, &dn ) != 0 ||
+ slapi_pblock_get(pb, SLAPI_PLUGIN_OPRETURN, &oprc) != 0)
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM,
+ "referint_postop_del: could not get parameters\n" );
+ return( -1 );
+ }
+
+ /* this plugin should only execute if the delete was successful
+ and this is not a replicated op
+ */
+ if(oprc != 0 || isrepop)
+ {
+ return( 0 );
+ }
+ /* get args */
+ if ( slapi_pblock_get( pb, SLAPI_PLUGIN_ARGC, &argc ) != 0) {
+ slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM,
+ "referint_postop failed to get argc\n" );
+ return( -1 );
+ }
+ if ( slapi_pblock_get( pb, SLAPI_PLUGIN_ARGV, &argv ) != 0) {
+ slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM,
+ "referint_postop failed to get argv\n" );
+ return( -1 );
+ }
+
+ if(argv == NULL){
+ slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM,
+ "referint_postop_modrdn, args are NULL\n" );
+ return( -1 );
+ }
+
+ if (argc >= 3) {
+ /* argv[0] will be the delay */
+ delay = atoi(argv[0]);
+
+ /* argv[2] will be wether or not to log changes */
+ logChanges = atoi(argv[2]);
+
+ if(delay == -1){
+ /* integrity updating is off */
+ rc = 0;
+ }else if(delay == 0){
+ /* no delay */
+ /* call function to update references to entry */
+ rc = update_integrity(argv, dn, NULL, logChanges);
+ }else{
+ /* write the entry to integrity log */
+ writeintegritylog(argv[1],dn, NULL);
+ rc = 0;
+ }
+ } else {
+ slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM,
+ "referint_postop insufficient arguments supplied\n" );
+ return( -1 );
+ }
+
+ return( rc );
+
+}
+
+int
+referint_postop_modrdn( Slapi_PBlock *pb )
+{
+ char *dn;
+ char *newrdn;
+ int oprc;
+ int rc;
+ char **argv;
+ int argc = 0;
+ int delay;
+ int logChanges=0;
+ int isrepop = 0;
+
+ if ( slapi_pblock_get( pb, SLAPI_IS_REPLICATED_OPERATION, &isrepop ) != 0 ||
+ slapi_pblock_get( pb, SLAPI_MODRDN_TARGET, &dn ) != 0 ||
+ slapi_pblock_get( pb, SLAPI_MODRDN_NEWRDN, &newrdn ) != 0 ||
+ slapi_pblock_get(pb, SLAPI_PLUGIN_OPRETURN, &oprc) != 0 ){
+
+ slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM,
+ "referint_postop_modrdn: could not get parameters\n" );
+ return( -1 );
+ }
+
+ /* this plugin should only execute if the delete was successful
+ and this is not a replicated op
+ */
+ if(oprc != 0 || isrepop){
+ return( 0 );
+ }
+ /* get args */
+ if ( slapi_pblock_get( pb, SLAPI_PLUGIN_ARGC, &argc ) != 0) {
+ slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM,
+ "referint_postop failed to get argv\n" );
+ return( -1 );
+ }
+ if ( slapi_pblock_get( pb, SLAPI_PLUGIN_ARGV, &argv ) != 0) {
+ slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM,
+ "referint_postop failed to get argv\n" );
+ return( -1 );
+ }
+
+ if(argv == NULL){
+ slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM,
+ "referint_postop_modrdn, args are NULL\n" );
+ return( -1 );
+ }
+
+ if (argc >= 3) {
+ /* argv[0] will always be the delay */
+ delay = atoi(argv[0]);
+
+ /* argv[2] will be wether or not to log changes */
+ logChanges = atoi(argv[2]);
+ } else {
+ slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM,
+ "referint_postop_modrdn insufficient arguments supplied\n" );
+ return( -1 );
+ }
+
+ if(delay == -1){
+ /* integrity updating is off */
+ rc = 0;
+ }else if(delay == 0){
+ /* no delay */
+ /* call function to update references to entry */
+ rc = update_integrity(argv, dn, newrdn, logChanges);
+ }else{
+ /* write the entry to integrity log */
+ writeintegritylog(argv[1],dn, newrdn);
+ rc = 0;
+ }
+
+ return( rc );
+}
+
+int isFatalSearchError(int search_result)
+{
+
+ /* Make sure search result is fatal
+ * Some conditions that happen quite often are not fatal
+ * for example if you have two suffixes and one is null, you will always
+ * get no such object, howerever this is not a fatal error.
+ * Add other conditions to the if statement as they are found
+ */
+
+ /* NPCTE fix for bugid 531225, esc 0. <P.R> <30-May-2001> */
+ switch(search_result) {
+ case LDAP_REFERRAL:
+ case LDAP_NO_SUCH_OBJECT: return 0 ;
+ }
+ return 1;
+ /* end of NPCTE fix for bugid 531225 */
+
+}
+
+int update_integrity(char **argv, char *origDN, char *newrDN, int logChanges){
+
+ Slapi_PBlock *search_result_pb = NULL;
+ Slapi_PBlock *mod_result_pb = NULL;
+ Slapi_Entry **search_entries = NULL;
+ int search_result;
+ Slapi_DN *sdn = NULL;
+ void *node = NULL;
+ LDAPMod attribute1, attribute2;
+ const LDAPMod *list_of_mods[3];
+ char *values_del[2];
+ char *values_add[2];
+ char *filter = NULL;
+ int i, j;
+ const char *search_base = NULL;
+ char *newDN=NULL;
+ char **dnParts=NULL;
+ int dnsize;
+ int x;
+ int rc;
+ int valcount = 0;
+
+ if ( argv == NULL ) {
+ slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM,
+ "referint_postop required config file arguments missing\n" );
+ rc = -1;
+ goto free_and_return;
+ }
+
+ /* for now, just putting attributes to keep integrity on in conf file,
+ until resolve the other timing mode issue */
+
+ /* Search each namingContext in turn */
+ for ( sdn = slapi_get_first_suffix( &node, 0 ); sdn != NULL;
+ sdn = slapi_get_next_suffix( &node, 0 ))
+ {
+ search_base = slapi_sdn_get_dn( sdn );
+
+
+ for(i=3; argv[i] != NULL; i++)
+ {
+ unsigned long filtlen = strlen(argv[i]) + (strlen(origDN) * 3 ) + 4;
+ filter = (char *)slapi_ch_calloc( filtlen, sizeof(char ));
+ if (( search_result = ldap_create_filter( filter, filtlen, "(%a=%e)",
+ NULL, NULL, argv[i], origDN, NULL )) == LDAP_SUCCESS ) {
+
+ /* Don't need any attribute */
+ char * attrs[2];
+ attrs[0]="1.1";
+ attrs[1]=NULL;
+
+ /* Use new search API */
+ search_result_pb = slapi_pblock_new();
+ slapi_search_internal_set_pb(search_result_pb, search_base, LDAP_SCOPE_SUBTREE,
+ filter, attrs, 0 /* attrs only */, NULL,NULL,referint_plugin_identity,0);
+ slapi_search_internal_pb(search_result_pb);
+
+ slapi_pblock_get( search_result_pb, SLAPI_PLUGIN_INTOP_RESULT, &search_result);
+ }
+
+
+ /* if search successfull then do integrity update */
+ if(search_result == 0)
+ {
+ slapi_pblock_get( search_result_pb,SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES,
+ &search_entries);
+
+ for(j=0; search_entries[j] != NULL; j++)
+ {
+ /* no matter what mode in always going to delete old dn so set that up */
+ values_del[0]= origDN;
+ values_del[1]= NULL;
+ attribute1.mod_type = argv[i];
+ attribute1.mod_op = LDAP_MOD_DELETE;
+ attribute1.mod_values = values_del;
+ list_of_mods[0] = &attribute1;
+
+ if(newrDN == NULL){
+ /* in delete mode so terminate list of mods cause this is the only one */
+ list_of_mods[1] = NULL;
+ }else if(newrDN != NULL){
+ /* in modrdn mode */
+
+ /* need to put together rdn into a dn */
+ dnParts = ldap_explode_dn( origDN, 0 );
+
+ /* skip original rdn so start at 1*/
+ dnsize = 0;
+ for(x=1; dnParts[x] != NULL; x++)
+ {
+ /* +2 for space and comma adding later */
+ dnsize += strlen(dnParts[x]) + 2;
+ }
+ /* add the newrDN length */
+ dnsize += strlen(newrDN) + 1;
+
+ newDN = slapi_ch_calloc(dnsize, sizeof(char));
+ strcat(newDN, newrDN);
+ for(x=1; dnParts[x] != NULL; x++)
+ {
+ strcat(newDN, ", ");
+ strcat(newDN, dnParts[x]);
+ }
+
+ values_add[0]=newDN;
+ values_add[1]=NULL;
+ attribute2.mod_type = argv[i];
+ attribute2.mod_op = LDAP_MOD_ADD;
+ attribute2.mod_values = values_add;
+
+ /* add the new dn to list of mods and terminate list of mods */
+ list_of_mods[1] = &attribute2;
+ list_of_mods[2] = NULL;
+
+ }
+
+ /* try to cleanup entry */
+
+ /* Use new internal operation API */
+ mod_result_pb=slapi_pblock_new();
+ slapi_modify_internal_set_pb(mod_result_pb,slapi_entry_get_dn(search_entries[j]),
+ (LDAPMod **)list_of_mods,NULL,NULL,referint_plugin_identity,0);
+ slapi_modify_internal_pb(mod_result_pb);
+
+ /* could check the result code here if want to log it or something later
+ for now, continue no matter what result is */
+
+ slapi_pblock_destroy(mod_result_pb);
+
+ /* cleanup memory allocated for dnParts and newDN */
+ if(dnParts != NULL){
+ for(x=0; dnParts[x] != NULL; x++)
+ {
+ free(dnParts[x]);
+ }
+ free(dnParts);
+ }
+ if(newDN != NULL){
+ slapi_ch_free((void**)&newDN);
+ }
+
+ }
+
+
+
+ }else{
+ if(isFatalSearchError(search_result))
+ {
+ /* NPCTE fix for bugid 531225, esc 0. <P.R> <30-May-2001> */
+ slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM,
+ "referint_postop search (base=%s filter=%s) returned error %d\n", search_base,filter,search_result );
+ /* end of NPCTE fix for bugid 531225 */
+ rc = -1;
+ goto free_and_return;
+ }
+
+ }
+
+ slapi_ch_free((void**)&filter);
+
+ if(search_result_pb != NULL){
+ slapi_free_search_results_internal(search_result_pb);
+ slapi_pblock_destroy(search_result_pb);
+ search_result_pb= NULL;
+ }
+
+ }
+ }
+ /* if got here, then everything good rc = 0 */
+ rc = 0;
+
+free_and_return:
+
+ /* free filter and search_results_pb */
+ if(filter != NULL)
+ {
+ free(filter);
+ }
+
+ if(search_result_pb != NULL)
+ {
+ slapi_free_search_results_internal(search_result_pb);
+ free(search_result_pb);
+ }
+
+ return(rc);
+}
+
+int referint_postop_start( Slapi_PBlock *pb)
+{
+
+ char **argv;
+ int argc = 0;
+
+ /* get args */
+ if ( slapi_pblock_get( pb, SLAPI_PLUGIN_ARGC, &argc ) != 0 ) {
+ slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM,
+ "referint_postop failed to get argv\n" );
+ return( -1 );
+ }
+ if ( slapi_pblock_get( pb, SLAPI_PLUGIN_ARGV, &argv ) != 0 ) {
+ slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM,
+ "referint_postop failed to get argv\n" );
+ return( -1 );
+ }
+
+ if(argv == NULL){
+ slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM,
+ "args were null in referint_postop_start\n" );
+ return( -1 );
+ }
+
+ /* only bother to start the thread if you are in delay mode.
+ 0 = no delay,
+ -1 = integrity off */
+
+ if (argc >= 1) {
+ if(atoi(argv[0]) > 0){
+
+ /* initialize cv and lock */
+
+ referint_mutex = PR_NewLock();
+ keeprunning_mutex = PR_NewLock();
+ keeprunning_cv = PR_NewCondVar(keeprunning_mutex);
+ keeprunning =1;
+
+ if (( referint_tid = PR_CreateThread (PR_USER_THREAD,
+ referint_thread_func,
+ (void *)argv,
+ PR_PRIORITY_NORMAL,
+ PR_GLOBAL_THREAD,
+ PR_UNJOINABLE_THREAD,
+ SLAPD_DEFAULT_THREAD_STACKSIZE)) == NULL ) {
+ slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM,
+ "referint_postop_start PR_CreateThread failed\n" );
+ exit( 1 );
+ }
+ }
+ } else {
+ slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM,
+ "referint_postop_start insufficient arguments supplied\n" );
+ return( -1 );
+ }
+
+ return(0);
+
+}
+
+int referint_postop_close( Slapi_PBlock *pb)
+{
+
+ /* signal the thread to exit */
+ if (NULL != keeprunning_mutex) {
+ PR_Lock(keeprunning_mutex);
+ keeprunning=0;
+ if (NULL != keeprunning_cv) {
+ PR_NotifyCondVar(keeprunning_cv);
+ }
+ PR_Unlock(keeprunning_mutex);
+ }
+
+ return(0);
+}
+
+void referint_thread_func(void *arg){
+
+ char **plugin_argv = (char **)arg;
+ PRFileDesc *prfd;
+ char *logfilename;
+ char thisline[MAX_LINE];
+ int delay;
+ int no_changes;
+ char delimiter[]="\t\n";
+ char *ptoken;
+ char *tmpdn, *tmprdn;
+ int logChanges=0;
+ char * iter = NULL;
+
+ if(plugin_argv == NULL){
+ slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM,
+ "referint_thread_func not get args \n" );
+ return;
+ }
+
+
+ delay = atoi(plugin_argv[0]);
+ logfilename = plugin_argv[1];
+ logChanges = atoi(plugin_argv[2]);
+
+ /* keep running this thread until plugin is signalled to close */
+
+ while(1){
+
+ no_changes=1;
+
+ while(no_changes){
+
+ PR_Lock(keeprunning_mutex);
+ if(keeprunning == 0){
+ PR_Unlock(keeprunning_mutex);
+ break;
+ }
+ PR_Unlock(keeprunning_mutex);
+
+
+ PR_Lock(referint_mutex);
+ if (( prfd = PR_Open( logfilename, PR_RDONLY,
+ REFERINT_DEFAULT_FILE_MODE )) == NULL )
+ {
+ PR_Unlock(referint_mutex);
+ /* go back to sleep and wait for this file */
+ PR_Lock(keeprunning_mutex);
+ PR_WaitCondVar(keeprunning_cv, PR_SecondsToInterval(delay));
+ PR_Unlock(keeprunning_mutex);
+ }else{
+ no_changes = 0;
+ }
+ }
+
+ /* check keep running here, because after break out of no
+ * changes loop on shutdown, also need to break out of this
+ * loop before trying to do the changes. The server
+ * will pick them up on next startup as file still exists
+ */
+ PR_Lock(keeprunning_mutex);
+ if(keeprunning == 0){
+ PR_Unlock(keeprunning_mutex);
+ break;
+ }
+ PR_Unlock(keeprunning_mutex);
+
+
+ while( GetNextLine(thisline, MAX_LINE, prfd) ){
+ ptoken = ldap_utf8strtok_r(thisline, delimiter, &iter);
+ tmpdn = slapi_ch_calloc(strlen(ptoken) + 1, sizeof(char));
+ strcpy(tmpdn, ptoken);
+
+ ptoken = ldap_utf8strtok_r (NULL, delimiter, &iter);
+ if(!strcasecmp(ptoken, "NULL")){
+ tmprdn = NULL;
+ }else{
+ tmprdn = slapi_ch_calloc(strlen(ptoken) + 1, sizeof(char));
+ strcpy(tmprdn, ptoken);
+ }
+
+
+ update_integrity(plugin_argv, tmpdn, tmprdn, logChanges);
+
+ slapi_ch_free((void **) &tmpdn);
+ slapi_ch_free((void **) &tmprdn);
+ }
+
+ PR_Close(prfd);
+
+ /* remove the original file */
+ if( PR_SUCCESS != PR_Delete(logfilename) )
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM,
+ "referint_postop_close could not delete \"%s\"\n",
+ logfilename );
+ }
+
+ /* unlock and let other writers back at the file */
+ PR_Unlock(referint_mutex);
+
+ /* wait on condition here */
+ PR_Lock(keeprunning_mutex);
+ PR_WaitCondVar(keeprunning_cv, PR_SecondsToInterval(delay));
+ PR_Unlock(keeprunning_mutex);
+ }
+
+ /* cleanup resources allocated in start */
+ if (NULL != keeprunning_mutex) {
+ PR_DestroyLock(keeprunning_mutex);
+ }
+ if (NULL != referint_mutex) {
+ PR_DestroyLock(referint_mutex);
+ }
+ if (NULL != keeprunning_cv) {
+ PR_DestroyCondVar(keeprunning_cv);
+ }
+
+
+}
+
+int my_fgetc(PRFileDesc *stream)
+{
+ static char buf[READ_BUFSIZE] = "\0";
+ static int position = READ_BUFSIZE;
+ int retval;
+ int err;
+
+ /* check if we need to load the buffer */
+
+ if( READ_BUFSIZE == position )
+ {
+ memset(buf, '\0', READ_BUFSIZE);
+ if( ( err = PR_Read(stream, buf, READ_BUFSIZE) ) >= 0)
+ {
+ /* it read some data */;
+ position = 0;
+ }else{
+ /* an error occurred */
+ return err;
+ }
+ }
+
+ /* try to read some data */
+ if( '\0' == buf[position])
+ {
+ /* out of data, return eof */
+ retval = MY_EOF;
+ position = READ_BUFSIZE;
+ }else{
+ retval = buf[position];
+ position++;
+ }
+
+ return retval;
+
+}
+
+int
+GetNextLine(char *dest, int size_dest, PRFileDesc *stream) {
+
+ char nextchar ='\0';
+ int done = 0;
+ int i = 0;
+
+ while(!done)
+ {
+ if( ( nextchar = my_fgetc(stream) ) != 0)
+ {
+ if( i < (size_dest - 1) )
+ {
+ dest[i] = nextchar;
+ i++;
+
+ if(nextchar == '\n')
+ {
+ /* end of line reached */
+ done = 1;
+ }
+
+ }else{
+ /* no more room in buffer */
+ done = 1;
+ }
+
+ }else{
+ /* error or end of file */
+ done = 1;
+ }
+ }
+
+ dest[i] = '\0';
+
+ /* return size of string read */
+ return i;
+}
+
+void writeintegritylog(char *logfilename, char *dn, char *newrdn){
+ PRFileDesc *prfd;
+ char buffer[MAX_LINE];
+ int len_to_write = 0;
+ int rc;
+ /* write this record to the file */
+
+ /* use this lock to protect file data when update integrity is occuring */
+ /* should hopefully not be a big issue on concurrency */
+
+ PR_Lock(referint_mutex);
+ if (( prfd = PR_Open( logfilename, PR_WRONLY | PR_CREATE_FILE | PR_APPEND,
+ REFERINT_DEFAULT_FILE_MODE )) == NULL )
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM,
+ "referint_postop could not write integrity log \"%s\" "
+ SLAPI_COMPONENT_NAME_NSPR " %d (%s)\n",
+ logfilename, PR_GetError(), slapd_pr_strerror(PR_GetError()) );
+
+ PR_Unlock(referint_mutex);
+ return;
+ }
+
+ /* make sure we have enough room in our buffer
+ before trying to write it
+ */
+
+ /* add length of dn + 4(two tabs, a newline, and terminating \0) */
+ len_to_write = strlen(dn) + 4;
+
+ if(newrdn == NULL)
+ {
+ /* add the length of "NULL" */
+ len_to_write += 4;
+ }else{
+ /* add the length of the newrdn */
+ len_to_write += strlen(newrdn);
+ }
+
+ if(len_to_write > MAX_LINE )
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, REFERINT_PLUGIN_SUBSYSTEM,
+ "referint_postop could not write integrity log:"
+ " line length exceeded. It will not be able"
+ " to update references to this entry.\n");
+ }else{
+ PRInt32 rv;
+ sprintf(buffer, "%s\t%s\t\n",
+ dn,
+ (newrdn != NULL) ? newrdn : "NULL");
+ if ((rv = PR_Write(prfd,buffer,strlen(buffer))) < 0){
+ slapi_log_error(SLAPI_LOG_FATAL,REFERINT_PLUGIN_SUBSYSTEM,
+ " writeintegritylog: PR_Write failed : The disk"
+ " may be full or the file is unwritable :: NSPR error - %d\n",
+ PR_GetError());
+ }
+ }
+
+ /* If file descriptor is closed successfully, PR_SUCCESS */
+
+ rc = PR_Close(prfd);
+ if (rc != PR_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL,REFERINT_PLUGIN_SUBSYSTEM,
+ " writeintegritylog: failed to close the file"
+ " descriptor prfd; NSPR error - %d\n",
+ PR_GetError());
+ }
+ PR_Unlock(referint_mutex);
+ }
diff --git a/ldap/servers/plugins/referint/referint.def b/ldap/servers/plugins/referint/referint.def
new file mode 100644
index 00000000..75861791
--- /dev/null
+++ b/ldap/servers/plugins/referint/referint.def
@@ -0,0 +1,12 @@
+; BEGIN COPYRIGHT BLOCK
+; Copyright 2001 Sun Microsystems, Inc.
+; Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+; All rights reserved.
+; END COPYRIGHT BLOCK
+;
+DESCRIPTION 'Netscape Directory Server 7 Referint Plugin'
+CODE SHARED READ EXECUTE
+DATA SHARED READ WRITE
+EXPORTS
+ referint_postop_init @2
+ plugin_init_debug_level @3
diff --git a/ldap/servers/plugins/replication/Makefile b/ldap/servers/plugins/replication/Makefile
new file mode 100644
index 00000000..6f5341e8
--- /dev/null
+++ b/ldap/servers/plugins/replication/Makefile
@@ -0,0 +1,152 @@
+#
+# BEGIN COPYRIGHT BLOCK
+# Copyright 2001 Sun Microsystems, Inc.
+# Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+# All rights reserved.
+# END COPYRIGHT BLOCK
+#
+#
+# GNU Makefile for Directory Server "Replication" plugin
+#
+#
+
+LDAP_SRC = ../../..
+MCOM_ROOT = ../../../../..
+
+NOSTDCLEAN=true # don't let nsconfig.mk define target clean
+NOSTDSTRIP=true # don't let nsconfig.mk define target strip
+NSPR20=true # probably should be defined somewhere else (not sure where)
+
+OBJDEST = $(OBJDIR)/lib/replication-plugin
+LIBDIR = $(LIB_RELDIR)
+
+include $(MCOM_ROOT)/ldapserver/nsconfig.mk
+include $(LDAP_SRC)/nsldap.mk
+include $(MCOM_ROOT)/ldapserver/ns_usedb.mk
+
+ifeq ($(ARCH), WINNT)
+DEF_FILE:=./replication.def
+endif
+
+CFLAGS += $(SLCFLAGS) -DSLAPD_LOGGING
+
+ifeq ($(ARCH), WINNT)
+CFLAGS += /WX
+endif
+
+ifdef TEST_CL5
+CFLAGS += -DTEST_CL5
+endif
+
+INCLUDES += -I$(LDAP_SRC)/servers/slapd -I$(DB_INCLUDE)
+
+LOCAL_OBJS= \
+ cl5_api.o \
+ cl5_clcache.o \
+ cl5_config.o \
+ cl5_init.o \
+ csnpl.o\
+ legacy_consumer.o \
+ llist.o\
+ repl5_agmt.o \
+ repl5_agmtlist.o \
+ repl5_backoff.o \
+ repl5_connection.o \
+ repl5_inc_protocol.o \
+ repl5_init.o\
+ repl5_protocol.o \
+ repl5_protocol_util.o \
+ repl5_replica.o\
+ repl5_replica_config.o\
+ repl5_ruv.o\
+ repl5_schedule.o \
+ repl5_tot_protocol.o \
+ repl5_total.o\
+ repl5_mtnode_ext.o\
+ repl5_plugins.o \
+ repl_add.o \
+ repl_bind.o \
+ repl_compare.o \
+ repl_connext.o \
+ repl_controls.o \
+ repl_delete.o \
+ repl_entry.o \
+ repl_ext.o \
+ repl_extop.o \
+ repl_globals.o \
+ repl_init.o \
+ repl_modify.o \
+ repl_modrdn.o \
+ repl_monitor.o \
+ repl_objset.o \
+ repl_opext.o \
+ repl_ops.o \
+ repl_rootdse.o \
+ repl_search.o \
+ replutil.o \
+ urp.o \
+ urp_glue.o \
+ urp_tombstone.o \
+ repl5_replica_hash.o\
+ repl5_replica_dnhash.o\
+ repl5_updatedn_list.o\
+
+LIBREPLICATION_OBJS = $(addprefix $(OBJDEST)/, $(LOCAL_OBJS))
+
+ifeq ($(ARCH), WINNT)
+REPLICATION_DLL_OBJ = $(addprefix $(OBJDEST)/, dllmain.o)
+endif
+
+LIBREPLICATION= $(addprefix $(LIBDIR)/, $(REPLICATION_DLL).$(DLL_SUFFIX))
+
+ifeq ($(ARCH), WINNT)
+EXTRA_LIBS_DEP += $(LIBSLAPD_DEP)
+EXTRA_LIBS_DEP += $(LDAPSDK_DEP) $(DB_LIB_DEP) $(NSPR_DEP)
+EXTRA_LIBS += $(LIBSLAPD) $(LDAPLINK) $(DB_LIB)
+endif
+
+ifeq ($(ARCH), AIX)
+EXTRA_LIBS_DEP += $(LIBSLAPD_DEP)
+EXTRA_LIBS_DEP += $(LDAPSDK_DEP) $(DB_LIB_DEP) $(NSPR_DEP)
+EXTRA_LIBS += $(LIBSLAPD) $(LDAP_SDK_LIBLDAP_DLL) $(DB_LIB)
+endif
+
+ifeq ($(ARCH), HPUX)
+EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAPSDK_DEP) $(NSPR_DEP) $(SECURITY_DEP)
+EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(SECURITYLINK) $(NSPRLINK) $(ICULINK)
+endif
+
+ifeq ($(ARCH), WINNT)
+DLL_LDFLAGS += -def:"./replication.def"
+endif # WINNT
+
+ifeq ($(ARCH), AIX)
+EXTRA_LIBS += $(DLL_EXTRA_LIBS)
+LD=ld
+endif
+
+clientSDK:
+
+all: $(OBJDEST) $(LIBDIR) $(LIBREPLICATION)
+
+$(LIBREPLICATION): $(LIBREPLICATION_OBJS) $(REPLICATION_DLL_OBJ) $(DEF_FILE)
+ $(LINK_DLL) $(LIBREPLICATION_OBJS) $(REPLICATION_DLL_OBJ) $(PLATFORMLIBS) $(EXTRA_LIBS) $(LDAP_LIBLDIF) $(NSPRLINK)
+
+tests: $(TEST_PROGS)
+
+veryclean: clean
+
+clean:
+ $(RM) $(LIBREPLICATION_OBJS)
+ifeq ($(ARCH), WINNT)
+ $(RM) $(REPLICATION_DLL_OBJ)
+endif
+ $(RM) $(LIBREPLICATION)
+
+$(OBJDEST):
+ $(MKDIR) $(OBJDEST)
+
+#
+# header file dependencies (incomplete)
+#
+$(LIBREPLICATION_OBJS):
diff --git a/ldap/servers/plugins/replication/cl4.h b/ldap/servers/plugins/replication/cl4.h
new file mode 100644
index 00000000..0dbcece2
--- /dev/null
+++ b/ldap/servers/plugins/replication/cl4.h
@@ -0,0 +1,65 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* cl4.h - global declarations used by the 4.0 style changelog module
+ */
+
+#ifndef CL4_H
+#define CL4_H
+
+#include "slapi-private.h"
+#include "portable.h" /* GGOODREPL - is this cheating? */
+
+#define CONFIG_CHANGELOG_SUFFIX_ATTRIBUTE "nsslapd-changelogsuffix"
+
+/* A place to store changelog config info */
+typedef struct _chglog4Info chglog4Info;
+
+/* in cl4.c */
+chglog4Info* changelog4_new (Slapi_Entry *e, char *errorbuf);
+void changelog4_free (chglog4Info** cl4);
+void changelog4_lock (Object *obj, PRBool write);
+void changelog4_unlock (Object *obj);
+const char * changelog4_get_dir (const chglog4Info* cl4);
+const char * changelog4_get_suffix (const chglog4Info* cl4);
+time_t changelog4_get_maxage (const chglog4Info* cl4);
+unsigned long changelog4_get_maxentries (const chglog4Info* cl4);
+void changelog4_set_dir (chglog4Info* cl4, const char *dir);
+void changelog4_set_suffix (chglog4Info* cl4, const char *suffix);
+void changelog4_set_maxage (chglog4Info* cl4, const char *maxage);
+void changelog4_set_maxentries (chglog4Info* cl4, const char* maxentries);
+
+/* In cl4_suffix.c */
+char *get_changelog_dataversion(const chglog4Info* cl4);
+void set_changelog_dataversion(chglog4Info* cl4, const char *dataversion);
+
+/* In cl4_config.c */
+int changelog4_config_init();
+void changelog4_config_destroy();
+
+/*
+ * backend configuration information
+ * Previously, these two typedefs were in ../../slapd/slapi-plugin.h but
+ * the CL4 code is the only remaining code that references these definitions.
+ */
+typedef struct config_directive
+{
+ char *file_name; /* file from which to read directive */
+ int lineno; /* line to read */
+ int argc; /* number of argvs */
+ char **argv; /* directive in agrv format */
+} slapi_config_directive;
+
+typedef struct be_config
+{
+ char *type; /* type of the backend */
+ char *suffix; /* suffix of the backend */
+ int is_private; /* 1 - private, 0 -not */
+ int log_change; /* 1 - write change to the changelog; 0 - don't */
+ slapi_config_directive *directives;/* configuration directives */
+ int dir_count; /* number of directives */
+} slapi_be_config;
+
+#endif
diff --git a/ldap/servers/plugins/replication/cl4_api.c b/ldap/servers/plugins/replication/cl4_api.c
new file mode 100644
index 00000000..135b4a5b
--- /dev/null
+++ b/ldap/servers/plugins/replication/cl4_api.c
@@ -0,0 +1,797 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* cl4_api.h - implementation of the minimal interface to 4.0 changelog necessary to
+ link 4.0 changelog to 5.0 replication
+ */
+
+#include "repl.h"
+#include "cl4_api.h"
+#include "csnpl.h"
+#include "cl4.h"
+
+/*** Data Structures ***/
+
+/* changelog internal data */
+typedef struct cl4priv
+{
+ CSNPL *csnPL; /* csn pending list */
+ int regID; /* csn function registration id */
+}CL4Private;
+
+/* callback data to get result of internal operations */
+typedef struct cl4ret
+{
+ int err; /* error code */
+ Slapi_Entry *e; /* target entry */
+}CL4Ret;
+
+/* Global Data */
+static CL4Private s_cl4Desc; /* represents changelog state */
+
+/*** Helper functions forward declarations ***/
+static int _cl4WriteOperation (const slapi_operation_parameters *op);
+static void _cl4AssignCSNCallback (const CSN *csn, void *data);
+static void _cl4AbortCSNCallback (const CSN *csn, void *data);
+static char* _cl4MakeCSNDN (const CSN* csn);
+static int _cl4GetEntry (const CSN *csn, Slapi_Entry **entry);
+static void _cl4ResultCallback (int err, void *callback_data);
+static int _cl4EntryCallback (Slapi_Entry *e, void *callback_data);
+static PRBool _cl4CanAssignChangeNumber (const CSN *csn);
+static int _cl4ResolveTargetDN (Slapi_Entry *entry, Slapi_DN **newTargetDN);
+static int _cl4GetTargetEntry (Slapi_DN *targetDN, const char *uniqueid, Slapi_Entry **entry);
+static int _cl4FindTargetDN (const CSN *csn, const char *uniqueid,
+ const Slapi_DN *targetSDN, Slapi_DN **newTargetDN);
+static int _cl4AssignChangeNumber (changeNumber *cnum);
+static int _cl4UpdateEntry (const CSN *csn, const char *changeType, const Slapi_DN *newTargetDN, changeNumber cnum);
+
+/*** API ***/
+int cl4Init ()
+{
+ s_cl4Desc.csnPL = csnplNew ();
+ if (s_cl4Desc.csnPL == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "cl4Init: failed to create CSN pending list\n");
+ return CL4_CSNPL_ERROR;
+ }
+
+ s_cl4Desc.regID = csnRegisterNewCSNCb(_cl4AssignCSNCallback, NULL,
+ _cl4AbortCSNCallback, NULL);
+
+ return CL4_SUCCESS;
+}
+
+void cl4Cleanup ()
+{
+ if (s_cl4Desc.regID >= 0)
+ {
+ csnRemoveNewCSNCb(s_cl4Desc.regID);
+ s_cl4Desc.regID = -1;
+ }
+
+ if (s_cl4Desc.csnPL == NULL)
+ csnplFree (&s_cl4Desc.csnPL);
+}
+
+int cl4WriteOperation (const slapi_operation_parameters *op)
+{
+ int rc;
+ ReplicaId rd;
+
+ if (op == NULL || !IsValidOperation (op))
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "cl4WriteEntry: invalid entry\n");
+ return CL4_BAD_DATA;
+ }
+
+ rc = _cl4WriteOperation (op);
+ if (rc != CL4_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "cl4WriteEntry: failed to write changelog entry\n");
+ return rc;
+ }
+
+ /* the entry is generated by this server - remove the entry from the pending list */
+ rd= csn_get_replicaid(op->csn);
+ if (rd == slapi_get_replicaid ())
+ {
+ rc = csnplRemove (s_cl4Desc.csnPL, op->csn);
+
+ if (rc != 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "cl4WriteEntry: failed to remove CSN from the pending list\n");
+ rc = CL4_CSNPL_ERROR;
+ }
+ }
+
+ return rc;
+}
+
+int cl4ChangeTargetDN (const CSN *csn, const char *newDN)
+{
+ Slapi_PBlock *pb;
+ char *changeEntryDN;
+ Slapi_Mods smods;
+ int res;
+
+ if (csn == NULL || newDN == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "cl4ChangeTargetDN: invalid argument\n");
+ return CL4_BAD_DATA;
+ }
+
+ /* construct dn of the change entry */
+ changeEntryDN = _cl4MakeCSNDN (csn);
+ if (changeEntryDN == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "cl4ChangeTargetDN: failed to construct change entry dn\n");
+ return CL4_MEMORY_ERROR;
+ }
+
+ pb = slapi_pblock_new ();
+
+ slapi_mods_init(&smods, 1);
+ slapi_mods_add(&smods, LDAP_MOD_REPLACE | LDAP_MOD_BVALUES, attr_targetdn,
+ strlen (newDN), newDN);
+ slapi_modify_internal_set_pb(pb, changeEntryDN, slapi_mods_get_ldapmods_byref(&smods),
+ NULL, NULL, repl_get_plugin_identity(PLUGIN_LEGACY_REPLICATION), 0);
+ slapi_modify_internal_pb (pb);
+
+ slapi_mods_done(&smods);
+ slapi_ch_free ((void**)&changeEntryDN);
+
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &res);
+ slapi_pblock_destroy(pb);
+
+ if (res != LDAP_SUCCESS)
+ {
+ char s[CSN_STRSIZE];
+ slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name,
+ "cl4ChangeTargetDN: an error occured while modifying change entry with csn %s: %s. "
+ "Logging of changes is disabled.\n", csn_as_string(csn,PR_FALSE,s), ldap_err2string(res));
+ /* GGOODREPL g_set_repl_backend( NULL ); */
+ return CL4_LDAP_ERROR;
+ }
+
+ return CL4_SUCCESS;
+}
+
+void cl4AssignChangeNumbers (time_t when, void *arg)
+{
+ int rc = CL4_SUCCESS;
+ Slapi_Entry *entry;
+ CSN *csn = NULL;
+ Slapi_DN *newTargetDN;
+ changeNumber cnum;
+ char *changetype;
+
+ /* we are looping though the entries ready to be commited updating there target dn
+ and assigning change numbers */
+ while (_cl4GetEntry (csn, &entry) == CL4_SUCCESS)
+ {
+ /* ONREPL - I think we need to free previous csn */
+ csn = csn_new_by_string(slapi_entry_attr_get_charptr (entry, attr_csn));
+ /* all conflicts involving this entry have been resolved */
+ if (_cl4CanAssignChangeNumber (csn))
+ {
+ /* figure out the name of the target entry that corresponds to change csn */
+ rc = _cl4ResolveTargetDN (entry, &newTargetDN);
+ slapi_entry_free (entry);
+ if (rc != CL4_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "cl4AssignChangeNumbers: failed to resolve target dn\n");
+ break;
+ }
+
+ _cl4AssignChangeNumber (&cnum);
+
+ changetype = slapi_entry_attr_get_charptr (entry, attr_changetype);
+
+ /* update change entry: write change number and remove csn attribute.
+ Note that we leave uniqueid in the entry to avoid an extra update.
+ This is ok since uniqueid is an operational attribute not returned
+ to the client by default. */
+ rc = _cl4UpdateEntry (csn, changetype, newTargetDN, cnum);
+ if (newTargetDN)
+ {
+ slapi_sdn_free (&newTargetDN);
+ }
+
+ slapi_ch_free ((void**)&changetype);
+
+ if (rc != CL4_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "cl4AssignChangeNumbers: failed to update changelog entry\n");
+ break;
+ }
+ }
+ else /* went too far */
+ {
+ slapi_entry_free (entry);
+ break;
+ }
+ }
+}
+
+
+/*** Helper Functions ***/
+
+/* adds new change record to 4.0 changelog */
+static int _cl4WriteOperation (const slapi_operation_parameters *op)
+{
+ int rc = CL4_SUCCESS, res;
+ char *changeEntryDN, *timeStr;
+ Slapi_Entry *e;
+ Slapi_PBlock *pb = NULL;
+ Slapi_Value *values[3];
+ char s[CSN_STRSIZE];
+
+ slapi_log_error (SLAPI_LOG_PLUGIN, repl_plugin_name,
+ "_cl4WriteEntry: writing change record with csn %s for dn: \"%s\"\n",
+ csn_as_string(op->csn,PR_FALSE,s), op->target_address.dn);
+
+ /* create change entry dn */
+ changeEntryDN = _cl4MakeCSNDN (op->csn);
+ if (changeEntryDN == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "_cl4WriteEntry: failed to create entry dn\n");
+ return CL4_MEMORY_ERROR;
+ }
+
+ /*
+ * Create the entry struct, and fill in fields common to all types
+ * of change records.
+ */
+ e = slapi_entry_alloc();
+ if (e == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "_cl4WriteEntry: failed to allocate change entry\n");
+ return CL4_MEMORY_ERROR;
+ }
+
+ slapi_entry_set_dn(e, slapi_ch_strdup (changeEntryDN));
+
+ /* Set the objectclass attribute */
+ values [0] = slapi_value_new (NULL);
+ values [1] = slapi_value_new (NULL);
+ values [2] = NULL;
+ slapi_value_set_string(values[0], "top");
+ slapi_value_set_string(values[1], "changelogentry");
+ slapi_entry_add_values_sv (e, "objectclass", values);
+
+ /* ONREPL - for now we have to free Slapi_Values since api makes copy;
+ this will change when a new set of api is added */
+ slapi_value_free (&(values[0]));
+ slapi_value_free (&(values[1]));
+
+ /* Set the changeNumber attribute */
+ /* Need to set this because it is required by schema */
+ slapi_entry_attr_set_charptr (e, attr_changenumber, "0");
+
+ /* Set the targetentrydn attribute */
+ if (op->operation_type == SLAPI_OPERATION_ADD) /* use raw dn */
+ slapi_entry_attr_set_charptr (e, attr_targetdn, slapi_entry_get_dn (op->p.p_add.target_entry));
+ else /* use normolized dn */
+ slapi_entry_attr_set_charptr (e, attr_targetdn, op->target_address.dn);
+
+ /* ONREPL - set dbid attribute */
+
+ /* Set the changeTime attribute */
+ timeStr = format_localTime (current_time());
+ slapi_entry_attr_set_charptr (e, attr_changetime, timeStr);
+ slapi_ch_free((void**)&timeStr);
+
+ /*
+ * Finish constructing the entry. How to do it depends on the type
+ * of modification being logged.
+ */
+ switch (op->operation_type)
+ {
+ case SLAPI_OPERATION_ADD: if (entry2reple(e, op->p.p_add.target_entry) != 0 )
+ {
+ rc = CL4_INTERNAL_ERROR;
+ goto done;
+ }
+
+ break;
+
+ case SLAPI_OPERATION_MODIFY: if (mods2reple(e, op->p.p_modify.modify_mods) != 0)
+ {
+ rc = CL4_INTERNAL_ERROR;
+ goto done;
+ }
+
+ break;
+
+ case SLAPI_OPERATION_MODDN: if (modrdn2reple(e, op->p.p_modrdn.modrdn_newrdn,
+ op->p.p_modrdn.modrdn_deloldrdn, op->p.p_modrdn.modrdn_mods) != 0)
+ {
+ rc = CL4_INTERNAL_ERROR;
+ goto done;
+ }
+
+ break;
+
+ case SLAPI_OPERATION_DELETE: /* Set the changetype attribute */
+ slapi_entry_attr_set_charptr (e, attr_changetype, "delete");
+ break;
+ }
+
+ pb = slapi_pblock_new (pb);
+ slapi_add_entry_internal_set_pb (pb, e, NULL, repl_get_plugin_identity (PLUGIN_LEGACY_REPLICATION), 0);
+ slapi_add_internal_pb (pb);
+
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &res);
+ slapi_pblock_destroy(pb);
+
+ if (res != LDAP_SUCCESS)
+ {
+ char s[CSN_STRSIZE];
+ slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name,
+ "_cl4WriteEntry: an error occured while adding change entry with csn %s, dn = %s: %s. "
+ "Logging of changes is disabled.\n", csn_as_string(op->csn,PR_FALSE,s), op->target_address.dn,
+ ldap_err2string(res));
+ /* GGOODREPL g_set_repl_backend( NULL ); */
+ rc = CL4_LDAP_ERROR;
+ }
+
+done:
+ if (changeEntryDN)
+ slapi_ch_free((void **) &changeEntryDN);
+
+ return rc;
+}
+
+static void _cl4AssignCSNCallback (const CSN *csn, void *data)
+{
+ int rc;
+
+ if (csn == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_cl4AssignCSNCallback: null csn\n");
+ return;
+ }
+
+ rc = csnplInsert (s_cl4Desc.csnPL, csn);
+
+ if (rc == -1)
+ {
+ char s[CSN_STRSIZE];
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "_cl4AssignCSNCallback: failed to insert csn %s to the pending list\n",
+ csn_as_string(csn,PR_FALSE,s));
+ }
+}
+
+static void _cl4AbortCSNCallback (const CSN *csn, void *data)
+{
+ int rc;
+
+ if (csn == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_cl4AbortCSNCallback: null csn\n");
+ return;
+ }
+
+ rc = csnplRemove (s_cl4Desc.csnPL, csn);
+ if (rc == -1)
+ {
+ char s[CSN_STRSIZE];
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "_cl4AbortCSNCallback: failed to remove csn %s from the pending list\n",
+ csn_as_string(csn,PR_FALSE,s));
+ }
+}
+
+/* initial dn format: csn=<csn>,<changelog suffix>. For instance, csn=013744022939465,cn=changelog4 */
+static char* _cl4MakeCSNDN (const CSN* csn)
+{
+ char *pat, *edn;
+ char *suffix = changelog4_get_suffix ();
+ char s[CSN_STRSIZE];
+
+ if (suffix == NULL)
+ return NULL;
+
+ /* Construct the dn of this change record */
+ pat = "%s=%s,%s";
+ edn = slapi_ch_malloc(strlen(pat) + strlen(attr_csn) + strlen(suffix) + CSN_STRSIZE + 1);
+ if (edn)
+ sprintf(edn, pat, attr_csn, csn_as_string(csn,PR_FALSE,s), suffix);
+ slapi_ch_free ((void **)&suffix);
+
+ return edn;
+}
+
+static int _cl4GetEntry (const CSN *csn, Slapi_Entry **entry)
+{
+ int rc;
+ char *suffix = changelog4_get_suffix ();
+ int type;
+ const char *value;
+ CL4Ret ret;
+ char s[CSN_STRSIZE];
+
+ if (csn == NULL) /* entry with smallest csn */
+ {
+ type = SLAPI_SEQ_FIRST;
+ value = NULL;
+ }
+ else /* entry with next csn */
+ {
+ type = SLAPI_SEQ_NEXT;
+ value = csn_as_string(csn,PR_FALSE,s);
+ }
+
+ rc = slapi_seq_callback(suffix, type, attr_csn, (char*)value, NULL, 0, &ret, NULL,
+ _cl4ResultCallback, _cl4EntryCallback, NULL);
+ slapi_ch_free ((void**)&suffix);
+
+ if (rc != 0 || ret.err != 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_cl4GetEntry: failed to get next changelog entry\n");
+ return CL4_INTERNAL_ERROR;
+ }
+
+ *entry = ret.e;
+ return CL4_SUCCESS;
+}
+
+static void _cl4ResultCallback (int err, void *callback_data)
+{
+ CL4Ret *ret = (CL4Ret *)callback_data;
+
+ if (ret)
+ {
+ ret->err = err;
+ }
+}
+
+static int _cl4EntryCallback (Slapi_Entry *e, void *callback_data)
+{
+ CL4Ret *ret = (CL4Ret *)callback_data;
+
+ if (ret)
+ {
+ ret->e = slapi_entry_dup (e);
+ }
+
+ return 0;
+}
+
+static PRBool _cl4CanAssignChangeNumber (const CSN *csn)
+{
+ CSN *commitCSN = NULL;
+
+ /* th CSN is withtin region that can be commited */
+ if (csn && csn_compare(csn, commitCSN) < 0)
+ return PR_TRUE;
+
+ return PR_FALSE;
+}
+
+/* ONREPL - describe algorithm */
+static int _cl4ResolveTargetDN (Slapi_Entry *entry, Slapi_DN **newTargetDN)
+{
+ int rc;
+ char *csnStr = slapi_entry_attr_get_charptr (entry, attr_csn);
+ char *targetdn = slapi_entry_attr_get_charptr (entry, attr_targetdn);
+ const char *uniqueid = slapi_entry_get_uniqueid (entry);
+ char *changetype = slapi_entry_attr_get_charptr (entry, attr_changetype);
+ CSN *csn = csn_new_by_string (csnStr);
+ Slapi_Entry *targetEntry = NULL;
+ const Slapi_DN *teSDN;
+ Slapi_DN *targetSDN;
+ const CSN *teDNCSN = NULL;
+
+ *newTargetDN = NULL;
+
+ targetSDN = slapi_sdn_new();
+ if (strcasecmp (changetype, "add") == 0) /* this is add operation - we have rawdn */
+ slapi_sdn_set_dn_byref (targetSDN, targetdn);
+ else
+ slapi_sdn_set_ndn_byref (targetSDN, targetdn);
+
+ /* read the entry to which the change was applied */
+ rc = _cl4GetTargetEntry (targetSDN, uniqueid, &targetEntry);
+ if (rc != CL4_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_cl4ResolveTargetDN: failed to get target entry\n");
+ goto done;
+ }
+
+ teDNCSN = entry_get_dncsn(targetEntry);
+ if (teDNCSN == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_cl4ResolveTargetDN: failed to get target entry dn\n");
+ rc = CL4_BAD_FORMAT;
+ goto done;
+ }
+
+ if (csn_compare(teDNCSN, csn) <= 0)
+ {
+ /* the change entry target dn should be the same as target entry dn */
+ teSDN = slapi_entry_get_sdn_const(targetEntry);
+
+ /* target dn of change entry is not the same as dn of the target entry - update */
+ if (slapi_sdn_compare (teSDN, targetSDN) != 0)
+ {
+ *newTargetDN = slapi_sdn_dup (targetSDN);
+ }
+ }
+ else /* the target entry was renamed since this change occur - find the right target dn */
+ {
+ rc = _cl4FindTargetDN (csn, uniqueid, targetSDN, newTargetDN);
+ }
+
+done:;
+ if (csnStr)
+ slapi_ch_free ((void**)&csnStr);
+
+ if (targetdn)
+ slapi_ch_free ((void**)&targetdn);
+
+ if (uniqueid)
+ slapi_ch_free ((void**)&uniqueid);
+
+ if (changetype)
+ slapi_ch_free ((void**)&changetype);
+
+ if (targetEntry)
+ slapi_entry_free (targetEntry);
+
+ if (targetSDN)
+ slapi_sdn_free (&targetSDN);
+
+ return rc;
+}
+
+static int _cl4GetTargetEntry (Slapi_DN *sdn, const char *uniqueid, Slapi_Entry **entry)
+{
+ Slapi_PBlock *pb;
+ char filter [128];
+ int res, rc = CL4_SUCCESS;
+ Slapi_Entry **entries = NULL;
+
+ /* read corresponding database entry based on its uniqueid */
+ sprintf (filter, "uniqueid=%s", uniqueid);
+ pb = slapi_pblock_new ();
+ slapi_search_internal_set_pb (pb, (char*)slapi_sdn_get_ndn(sdn), LDAP_SCOPE_SUBTREE, filter, NULL, 0, NULL, NULL,
+ repl_get_plugin_identity (PLUGIN_LEGACY_REPLICATION), 0);
+ slapi_search_internal_pb (pb);
+
+ if (pb == NULL)
+ {
+ rc = CL4_LDAP_ERROR;
+ goto done;
+ }
+
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &res);
+ if (res == LDAP_NO_SUCH_OBJECT) /* entry not found */
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_cl4GetTargetEntry: entry (%s) not found\n",
+ slapi_sdn_get_ndn(sdn));
+ rc = CL4_NOT_FOUND;
+ goto done;
+ }
+
+ if (res != LDAP_SUCCESS)
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name,
+ "_cl4ResolveTargetDN: an error occured while searching for directory entry with uniqueid %s: %s. "
+ "Logging of changes is disabled.\n", uniqueid, ldap_err2string(res));
+ /* GGOODREPL g_set_repl_backend( NULL ); */
+ rc = CL4_LDAP_ERROR;
+ goto done;
+ }
+
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries);
+ if (entries == NULL || entries [0] == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_cl4GetTargetEntry: entry (%s) not found\n",
+ slapi_sdn_get_ndn(sdn));
+ rc = CL4_NOT_FOUND;
+ goto done;
+ }
+
+ *entry = slapi_entry_dup (entries[0]);
+
+done:
+ if (pb)
+ {
+ slapi_free_search_results_internal(pb);
+ slapi_pblock_destroy (pb);
+ }
+
+ return rc;
+}
+
+static int _cl4FindTargetDN (const CSN *csn, const char *uniqueid,
+ const Slapi_DN *targetSDN, Slapi_DN **newTargetDN)
+{
+ int rc = CL4_SUCCESS;
+ int res, i;
+ Slapi_PBlock *pb;
+ char *suffix = changelog4_get_suffix ();
+ char filter [128];
+ Slapi_Entry **entries;
+ int minIndex = 0;
+ CSN *minCSN = NULL, *curCSN;
+ char *curType;
+ const Slapi_DN *sdn;
+ char s[CSN_STRSIZE];
+
+ *newTargetDN = NULL;
+
+ /* Look for all modifications to the target entry with csn larger than
+ this csn. We are only interested in rename operations, but change type
+ is currently not indexed */
+ sprintf (filter, "&(uniqueid=%s)(csn>%s)", uniqueid, csn_as_string(csn,PR_FALSE,s));
+ pb = slapi_pblock_new ();
+ slapi_search_internal_set_pb (pb, suffix, LDAP_SCOPE_SUBTREE, filter, NULL, 0, NULL, NULL,
+ repl_get_plugin_identity (PLUGIN_LEGACY_REPLICATION), 0);
+ slapi_search_internal_pb (pb);
+ slapi_ch_free ((void**)&suffix);
+ if (pb == NULL)
+ {
+ rc = CL4_LDAP_ERROR;
+ goto done;
+ }
+
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &res);
+ if (res == LDAP_NO_SUCH_OBJECT) /* entry not found */
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_cl4FindTargetDN: no entries much filter (%s)\n",
+ filter);
+ rc = CL4_NOT_FOUND;
+ goto done;
+ }
+
+ if (res != LDAP_SUCCESS)
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name,
+ "_cl4ResolveTargetDN: an error occured while searching change entries matching filter %s: %s. "
+ "Logging of changes is disabled.\n", filter, ldap_err2string(res));
+ /* GGOODREPL g_set_repl_backend( NULL ); */
+ rc = CL4_LDAP_ERROR;
+ goto done;
+ }
+
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries);
+ if (entries == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_cl4FindTargetDN: no entries much filter (%s)\n",
+ filter);
+ rc = CL4_NOT_FOUND;
+ goto done;
+ }
+
+ i = 0;
+
+ /* find rename operation with smallest csn - its target dn should be the name
+ of our change entry */
+ while (entries[i])
+ {
+ curType = slapi_entry_attr_get_charptr (entries[i], attr_changetype);
+ if (curType && strcasecmp (curType, "modrdn") == 0)
+ {
+ curCSN = csn_new_by_string (slapi_entry_attr_get_charptr (entries[i], attr_csn));
+ if (minCSN == NULL || csn_compare (curCSN, minCSN) < 0)
+ {
+ minCSN = curCSN;
+ minIndex = i;
+ }
+ }
+
+ if (curType)
+ slapi_ch_free ((void**)&curType);
+
+ i ++;
+ }
+
+ if (curCSN == NULL)
+ {
+ rc = CL4_NOT_FOUND;
+ goto done;
+ }
+
+ /* update targetDN of our entry if necessary */
+ sdn = slapi_entry_get_sdn_const(entries[minIndex]);
+
+ /* target dn does not match to renaming operation - rename change entry */
+ if (slapi_sdn_compare (sdn, targetSDN) != 0)
+ *newTargetDN = slapi_sdn_dup (sdn);
+
+done:
+ if (pb)
+ {
+ slapi_free_search_results_internal(pb);
+ slapi_pblock_destroy (pb);
+ }
+
+ return rc;
+}
+
+static int _cl4AssignChangeNumber (changeNumber *cnum)
+{
+ *cnum = ldapi_assign_changenumber();
+ return CL4_SUCCESS;
+}
+
+static int _cl4UpdateEntry (const CSN *csn, const char *changeType,
+ const Slapi_DN *newDN, changeNumber cnum)
+{
+ Slapi_PBlock *pb;
+ char *dn;
+ const char *dnTemp;
+ int res;
+ Slapi_Mods smods;
+ char cnumbuf[32];
+
+ if (csn == NULL || changeType == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_cl4UpdateEntry: invalid argument\n");
+ return CL4_BAD_DATA;
+ }
+
+ dn = _cl4MakeCSNDN (csn);
+ if (dn == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_cl4UpdateEntry: failed to create entry dn\n");
+ return CL4_MEMORY_ERROR;
+ }
+
+ slapi_mods_init(&smods, 2);
+ if (newDN)
+ {
+ if (strcasecmp (changeType, "add") == 0)
+ dnTemp = slapi_sdn_get_dn (newDN);
+ else
+ dnTemp = slapi_sdn_get_ndn (newDN);
+
+ slapi_mods_add(&smods, LDAP_MOD_REPLACE | LDAP_MOD_BVALUES, attr_targetdn,
+ strlen (dnTemp), dnTemp);
+ }
+ /* Set the changeNumber attribute */
+ sprintf(cnumbuf, "%lu", cnum);
+ slapi_mods_add (&smods, LDAP_MOD_REPLACE | LDAP_MOD_BVALUES, attr_changenumber,
+ strlen (cnumbuf), cnumbuf);
+ pb = slapi_pblock_new ();
+ slapi_modify_internal_set_pb (pb, dn, slapi_mods_get_ldapmods_byref(&smods), NULL, NULL,
+ repl_get_plugin_identity (PLUGIN_LEGACY_REPLICATION), 0);
+ slapi_modify_internal_pb (pb);
+ slapi_mods_done(&smods);
+ slapi_ch_free ((void**)&dn);
+
+ if (pb == NULL)
+ {
+ return CL4_LDAP_ERROR;
+ }
+
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &res);
+ slapi_pblock_destroy(pb);
+ if (res != LDAP_SUCCESS)
+ {
+ char s[CSN_STRSIZE];
+ slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name,
+ "cl4ChangeTargetDN: an error occured while modifying change entry with csn %s: %s. "
+ "Logging of changes is disabled.\n", csn_as_string(csn,PR_FALSE,s), ldap_err2string(res));
+ /* GGOODREPL g_set_repl_backend( NULL ); */
+ return CL4_LDAP_ERROR;
+ }
+
+ if ( ldapi_get_first_changenumber() == (changeNumber) 0L )
+ {
+ ldapi_set_first_changenumber( cnum );
+ }
+
+ ldapi_commit_changenumber(cnum);
+ return CL4_SUCCESS;
+}
diff --git a/ldap/servers/plugins/replication/cl4_api.h b/ldap/servers/plugins/replication/cl4_api.h
new file mode 100644
index 00000000..c0b20e57
--- /dev/null
+++ b/ldap/servers/plugins/replication/cl4_api.h
@@ -0,0 +1,67 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* cl4_api.h - minimal interface to 4.0 changelog necessary to link 4.0 changelog
+ to 5.0 replication
+ */
+
+#ifndef CL4_API_H
+#define CL4_API_H
+
+#include "repl.h"
+
+/*** Error Codes ***/
+enum
+{
+ CL4_SUCCESS,
+ CL4_BAD_DATA,
+ CL4_BAD_FORMAT,
+ CL4_NOT_FOUND,
+ CL4_MEMORY_ERROR,
+ CL4_CSNPL_ERROR,
+ CL4_LDAP_ERROR,
+ CL4_INTERNAL_ERROR
+};
+
+/*** APIs ***/
+/* Name: cl4Init
+ Description: initializes 4.0 changelog subsystem
+ Parameters: none
+ Return: ????
+ */
+int cl4Init ();
+
+/* Name: cl4WriteOperation
+ Description: logs operation to 4.0 changelog; operation must go through CD&R engine first
+ Parameters: op - operation to be logged
+
+ Return: ????
+ */
+int cl4WriteOperation (const slapi_operation_parameters *op);
+
+/* Name: cl4ChangeTargetDN
+ Description: modifies change entry target dn; should be called for conflicts due to naming collisions;
+ raw dn should be passed for add operations; normolized dn otherwise.
+ Parameters: csn - csn of the change entry to be modified
+ newDN - new target dn of the entry
+ Return: ????
+ */
+int cl4ChangeTargetDN (const CSN* csn, const char *newDN);
+
+/* Name: cl4AssignChangeNumbers
+ Description: this function should be called periodically to assign change numbers to changelog
+ entries. Intended for use with event queue
+ Parameters: parameters are not currently used
+ Return: none
+ */
+void cl4AssignChangeNumbers (time_t when, void *arg);
+
+/* Name: cl4Cleanup
+ Description: frees memory held by 4.0 changelog subsystem
+ Parameters: none
+ Return: none
+ */
+void cl4Clean ();
+#endif
diff --git a/ldap/servers/plugins/replication/cl4_init.c b/ldap/servers/plugins/replication/cl4_init.c
new file mode 100644
index 00000000..6c12f0b0
--- /dev/null
+++ b/ldap/servers/plugins/replication/cl4_init.c
@@ -0,0 +1,349 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+/* cl4_init.c - implments initialization/cleanup functions for
+ 4.0 style changelog
+ */
+
+#include <string.h>
+
+#include "slapi-plugin.h"
+#include "cl4.h"
+#include "repl.h"
+
+/* forward declarations */
+static int changelog4_create_be();
+static int changelog4_start_be ();
+static int changelog4_close();
+static int changelog4_remove();
+
+/*
+ * Initialise the 4.0 Changelog
+ */
+int changelog4_init ()
+{
+ int rc= 0; /* OK */
+ Slapi_Backend *rbe;
+ changeNumber first_change = 0UL, last_change = 0UL;
+ int lderr;
+
+ if (changelog4_create_be() < 0 )
+ {
+ rc= -1;
+ }
+ else
+ {
+ rc = changelog4_start_be ();
+ }
+
+ if(rc == 0)
+ {
+ rbe = get_repl_backend();
+ if(rbe!=NULL)
+ {
+ /* We have a Change Log. Check it's valid. */
+ /* changelog has to be started before its
+ data version can be read */
+ const char *sdv= get_server_dataversion();
+ const char *cdv= get_changelog_dataversion();
+ char *suffix = changelog4_get_suffix ();
+ if(!cdv || strcmp(sdv,cdv)!=0)
+ {
+
+ /* The SDV and CDV are not the same. The Change Log is invalid.
+ It must be removed. */
+ /* ONREPL - currently we go through this code when the changelog
+ is first created because we can't tell new backend from the
+ existing one.*/
+ rc = changelog4_close();
+ rc = changelog4_remove();
+
+ /* now restart the changelog */
+ changelog4_start_be ();
+
+ create_entity( suffix, "extensibleobject");
+ /* Write the Server Data Version onto the changelog suffix entry */
+ /* JCMREPL - And the changelog database version number */
+ set_changelog_dataversion(sdv);
+ slapi_ch_free ((void **)&suffix);
+
+ }
+ }
+ }
+
+ if(rc != 0)
+ {
+ slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name,
+ "An error occurred configuring the changelog database\n" );
+ }
+
+ first_change = replog_get_firstchangenum( &lderr );
+ last_change = replog_get_lastchangenum( &lderr );
+ ldapi_initialize_changenumbers( first_change, last_change );
+
+ return rc;
+}
+
+static int
+
+changelog4_close()
+{
+ int rc= 0 /* OK */;
+ Slapi_Backend *rbe= get_repl_backend();
+ Slapi_PBlock *pb = slapi_pblock_new ();
+ IFP closefn = NULL;
+
+ rc = slapi_be_getentrypoint (rbe, SLAPI_PLUGIN_CLOSE_FN, (void**)&closefn, pb);
+ if (rc != 0)
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name,
+ "Error: backend close entry point is missing. "
+ "Replication subsystem disabled.\n");
+ slapi_pblock_destroy (pb);
+ set_repl_backend( NULL );
+ return -1;
+ }
+
+ rc = closefn (pb);
+
+ if (rc != 0)
+ {
+
+ slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, "Error: the changelog database could "
+ "not be closed. Replication subsystem disabled.\n");
+ set_repl_backend( NULL );
+ rc = -1;
+ }
+
+ slapi_pblock_destroy (pb);
+ return rc;
+
+}
+
+static int
+changelog4_remove()
+{
+ int rc= 0 /* OK */;
+ Slapi_Backend *rbe= get_repl_backend();
+ Slapi_PBlock *pb = slapi_pblock_new ();
+ IFP rmdbfn = NULL;
+
+ rc = slapi_be_getentrypoint (rbe, SLAPI_PLUGIN_DB_RMDB_FN, (void**)&rmdbfn, pb);
+ if (rc != 0)
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name,
+ "Error: backend rmdb entry point is missing. "
+ "Replication subsystem disabled.\n");
+ slapi_pblock_destroy (pb);
+ set_repl_backend( NULL );
+ return -1;
+ }
+
+ rc = rmdbfn (pb);
+
+ if (rc != 0)
+ {
+
+ slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, "Error: the changelog database could "
+ "not be removed. Replication subsystem disabled.\n");
+ rc = -1;
+ }
+ else
+ {
+ slapi_log_error( SLAPI_LOG_REPL, repl_plugin_name, "New database generation computed. "
+ "Changelog database removed.\n");
+ }
+
+ slapi_pblock_destroy (pb);
+ return rc;
+}
+
+static Slapi_Backend *repl_backend = NULL;
+
+Slapi_Backend
+*get_repl_backend()
+{
+ return repl_backend;
+}
+
+void
+set_repl_backend(Slapi_Backend *be)
+{
+ repl_backend = be;
+}
+
+
+int changelog4_shutdown ()
+{
+ /* ONREPL - will shutdown the backend */
+ int rc = 1;
+
+ return rc;
+}
+
+static void changelog4_init_trimming ()
+{
+ char *cl_maxage = changelog4_get_maxage ();
+ unsigned long cl_maxentries = changelog4_get_maxentries ();
+ time_t ageval = age_str2time (cl_maxage);
+
+ slapi_ch_free ((void **)&cl_maxage);
+
+ init_changelog_trimming(cl_maxentries, ageval );
+}
+
+
+
+/*
+ * Function: changelog4_create_be
+ * Arguments: none
+ * Returns: 0 on success, non-0 on error
+ * Description: configures changelog backend instance.
+ */
+
+static int
+changelog4_create_be()
+{
+ int i, dir_count = 5;
+ Slapi_Backend *rbe;
+ slapi_be_config config;
+ char *cl_dir = changelog4_get_dir ();
+ char *cl_suffix;
+
+ if ( cl_dir == NULL ) {
+ slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name,
+ "Error: no directory specified for changelog database.\n");
+ return -1;
+ }
+
+ cl_suffix = changelog4_get_suffix ();
+
+ if ( cl_suffix == NULL ) {
+ slapi_ch_free ((void **)&cl_dir);
+ slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name,
+ "Error: no suffix specified for changelog database.\n");
+ return -1;
+ }
+
+ /* setup configuration parameters for backend initialization */
+ config.type = CHANGELOG_LDBM_TYPE;
+ config.suffix = cl_suffix;
+ config.is_private = 1; /* yes */
+ config.log_change = 0; /* no */
+ config.directives = (slapi_config_directive*)slapi_ch_calloc(
+ dir_count, sizeof(slapi_config_directive));
+ config.dir_count = dir_count;
+
+ for (i = 0; i < dir_count; i++)
+ {
+ config.directives[i].file_name = "(internal)";
+ config.directives[i].lineno = 0;
+ }
+
+ /* setup indexes */
+ config.directives[0].argv = NULL;
+ config.directives[0].argc = 3;
+ charray_add( &(config.directives[0].argv), slapi_ch_strdup( "index" ));
+ charray_add( &(config.directives[0].argv), slapi_ch_strdup( attr_changenumber ));
+ charray_add( &(config.directives[0].argv), slapi_ch_strdup( "eq" ));
+
+ /* Set up the database directory */
+ config.directives[1].argv = NULL;
+ config.directives[1].argc = 2;
+ charray_add( &(config.directives[1].argv), slapi_ch_strdup( "directory" ));
+ charray_add( &(config.directives[1].argv), slapi_ch_strdup( cl_dir ));
+
+ /* Override the entry cache size */
+ config.directives[2].argv = NULL;
+ config.directives[2].argc = 2;
+ charray_add( &(config.directives[2].argv), slapi_ch_strdup( "cachesize" ));
+ charray_add( &(config.directives[2].argv), slapi_ch_strdup( "10" ));
+
+ /* Override the database cache size */
+ config.directives[3].argv = NULL;
+ config.directives[3].argc = 2;
+ charray_add( &(config.directives[3].argv), slapi_ch_strdup( "dbcachesize" ));
+ charray_add( &(config.directives[3].argv), slapi_ch_strdup( "1000000" ));
+
+ /* Override the allids threshold */
+ config.directives[4].argv = NULL;
+ config.directives[4].argc = 2;
+ charray_add( &(config.directives[4].argv), slapi_ch_strdup( "allidsthreshold" ));
+ /* assumes sizeof(int) >= 32 bits */
+ charray_add( &(config.directives[4].argv), slapi_ch_strdup( "2147483647" ));
+
+ /* rbe = slapi_be_create_instance(&config, LDBM_TYPE); */
+ rbe= NULL;
+
+ /* free memory allocated to argv */
+ for (i = 0; i < dir_count; i++)
+ {
+ charray_free (config.directives[i].argv);
+ }
+
+ slapi_ch_free ((void **)&config.directives);
+ slapi_ch_free ((void **)&cl_dir);
+ slapi_ch_free ((void **)&cl_suffix);
+
+ if (rbe == NULL)
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name,
+ "Error: failed to create changelog backend. "
+ "Replication disabled.\n");
+ return -1;
+ }
+
+ set_repl_backend (rbe);
+
+ changelog4_init_trimming ();
+
+ return 0;
+}
+
+/* Name: changelog4_start_be
+ * Parameters: none
+ * Return: 0 if successful, non 0 otherwise
+ * Description: starts the changelog backend; backend must be configured
+ * first via call to changelog4_create_be
+ */
+static int
+changelog4_start_be ()
+{
+ int rc;
+ IFP startfn = NULL;
+ Slapi_PBlock *pb;
+ Slapi_Backend *rbe = get_repl_backend ();
+
+ if (rbe)
+ {
+ pb = slapi_pblock_new();
+ rc = slapi_be_getentrypoint(rbe, SLAPI_PLUGIN_START_FN, (void**)&startfn, pb);
+ if (rc != 0)
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name,
+ "Error: backend start entry point is missing. "
+ "Replication subsystem disabled.\n");
+ slapi_pblock_destroy (pb);
+ set_repl_backend( NULL );
+ return -1;
+ }
+
+ rc = startfn (pb);
+ slapi_pblock_destroy (pb);
+
+ if (rc != 0)
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name,
+ "Error: Failed to start changelog backend. "
+ "Replication subsystem disabled.\n");
+ set_repl_backend( NULL );
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
diff --git a/ldap/servers/plugins/replication/cl5.h b/ldap/servers/plugins/replication/cl5.h
new file mode 100644
index 00000000..a80c666b
--- /dev/null
+++ b/ldap/servers/plugins/replication/cl5.h
@@ -0,0 +1,38 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* cl5.h - changelog related function */
+
+#ifndef CL5_H
+#define CL5_H
+
+#include "cl5_api.h" /* changelog access APIs */
+
+typedef struct changelog5Config
+{
+ char *dir;
+/* These 2 parameters are needed for changelog trimming. Already present in 5.0 */
+ char *maxAge;
+ int maxEntries;
+/* the changelog DB configuration parameters are defined as CL5DBConfig in cl5_api.h */
+ CL5DBConfig dbconfig;
+}changelog5Config;
+
+/* initializes changelog*/
+int changelog5_init();
+/* cleanups changelog data */
+void changelog5_cleanup();
+/* initializes changelog configurationd */
+int changelog5_config_init();
+/* cleanups config data */
+void changelog5_config_cleanup();
+/* reads changelog configuration */
+int changelog5_read_config (changelog5Config *config);
+/* cleanups the content of the config structure */
+void changelog5_config_done (changelog5Config *config);
+/* frees the content and the config structure */
+void changelog5_config_free (changelog5Config **config);
+
+#endif
diff --git a/ldap/servers/plugins/replication/cl5_api.c b/ldap/servers/plugins/replication/cl5_api.c
new file mode 100644
index 00000000..792d3646
--- /dev/null
+++ b/ldap/servers/plugins/replication/cl5_api.c
@@ -0,0 +1,6512 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+/* cl5_api.c - implementation of 5.0 style changelog API */
+
+#include <errno.h>
+#include <sys/stat.h>
+#if defined( OS_solaris ) || defined( hpux )
+#include <sys/types.h>
+#include <sys/statvfs.h>
+#endif
+#if defined( linux )
+#include <sys/vfs.h>
+#endif
+
+
+#include "cl5_api.h"
+#include "plhash.h"
+
+#include "db.h"
+#include "cl5_clcache.h" /* To use the Changelog Cache */
+#include "repl5.h" /* for agmt_get_consumer_rid() */
+
+#define CL5_TYPE "Changelog5" /* changelog type */
+#define VERSION_SIZE 127 /* size of the buffer to hold changelog version */
+#define GUARDIAN_FILE "guardian" /* name of the guardian file */
+#define VERSION_FILE "DBVERSION" /* name of the version file */
+#define MAX_TRIALS 50 /* number of retries on db operations */
+#define V_5 5 /* changelog entry version */
+#define CHUNK_SIZE 64*1024
+#define DBID_SIZE 64
+#define FILE_SEP "_" /* separates parts of the db file name */
+
+#define T_CSNSTR "csn"
+#define T_UNIQUEIDSTR "nsuniqueid"
+#define T_PARENTIDSTR "parentuniqueid"
+#define T_NEWSUPERIORDNSTR "newsuperiordn"
+#define T_NEWSUPERIORIDSTR "newsuperioruniqueid"
+#define T_REPLGEN "replgen"
+
+#define ENTRY_COUNT_TIME 111 /* this time is used to construct csn
+ used to store/retrieve entry count */
+#define PURGE_RUV_TIME 222 /* this time is used to construct csn
+ used to store purge RUV vector */
+#define MAX_RUV_TIME 333 /* this time is used to construct csn
+ used to store upper boundary RUV vector */
+
+#define DB_EXTENSION_DB3 "db3"
+#define DB_EXTENSION "db4"
+
+#define HASH_BACKETS_COUNT 16 /* number of buckets in a hash table */
+
+#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR >= 4100
+#define DEFAULT_DB_OP_FLAGS DB_AUTO_COMMIT
+#define DB_OPEN(oflags, db, txnid, file, database, type, flags, mode, rval) \
+{ \
+ if (((oflags) & DB_INIT_TXN) && ((oflags) & DB_INIT_LOG)) \
+ { \
+ (rval) = (db)->open((db), (txnid), (file), (database), (type), (flags)|DB_AUTO_COMMIT, (mode)); \
+ } \
+ else \
+ { \
+ (rval) = (db)->open((db), (txnid), (file), (database), (type), (flags), (mode)); \
+ } \
+}
+#else /* older then db 41 */
+#define DEFAULT_DB_OP_FLAGS 0
+#define DB_OPEN(oflags, db, txnid, file, database, type, flags, mode, rval) \
+ (rval) = (db)->open((db), (file), (database), (type), (flags), (mode))
+#endif
+
+#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR >= 4000
+#define DB_ENV_SET_REGION_INIT(env) (env)->set_flags((env), DB_REGION_INIT, 1)
+#define DB_ENV_SET_TAS_SPINS(env, tas_spins) \
+ (env)->set_tas_spins((env), (tas_spins))
+#define TXN_BEGIN(env, parent_txn, tid, flags) \
+ (env)->txn_begin((env), (parent_txn), (tid), (flags))
+#define TXN_COMMIT(txn, flags) (txn)->commit((txn), (flags))
+#define TXN_ABORT(txn) (txn)->abort(txn)
+#define TXN_CHECKPOINT(env, kbyte, min, flags) \
+ (env)->txn_checkpoint((env), (kbyte), (min), (flags))
+#define MEMP_STAT(env, gsp, fsp, flags, malloc) \
+ (env)->memp_stat((env), (gsp), (fsp), (flags))
+#define MEMP_TRICKLE(env, pct, nwrotep) \
+ (env)->memp_trickle((env), (pct), (nwrotep))
+#define LOG_ARCHIVE(env, listp, flags, malloc) \
+ (env)->log_archive((env), (listp), (flags))
+#define LOG_FLUSH(env, lsn) (env)->log_flush((env), (lsn))
+#define LOCK_DETECT(env, flags, atype, aborted) \
+ (env)->lock_detect((env), (flags), (atype), (aborted))
+
+#else /* older than db 4.0 */
+#define DB_ENV_SET_REGION_INIT(env) db_env_set_region_init(1)
+#define DB_ENV_SET_TAS_SPINS(env, tas_spins) \
+ db_env_set_tas_spins((tas_spins))
+#define TXN_BEGIN(env, parent_txn, tid, flags) \
+ txn_begin((env), (parent_txn), (tid), (flags))
+#define TXN_COMMIT(txn, flags) txn_commit((txn), (flags))
+#define TXN_ABORT(txn) txn_abort((txn))
+#define TXN_CHECKPOINT(env, kbyte, min, flags) \
+ txn_checkpoint((env), (kbyte), (min), (flags))
+#define MEMP_TRICKLE(env, pct, nwrotep) memp_trickle((env), (pct), (nwrotep))
+#define LOG_FLUSH(env, lsn) log_flush((env), (lsn))
+#define LOCK_DETECT(env, flags, atype, aborted) \
+ lock_detect((env), (flags), (atype), (aborted))
+
+#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR >= 3300
+#define MEMP_STAT(env, gsp, fsp, flags, malloc) memp_stat((env), (gsp), (fsp))
+#define LOG_ARCHIVE(env, listp, flags, malloc) \
+ log_archive((env), (listp), (flags))
+
+#else /* older than db 3.3 */
+#define MEMP_STAT(env, gsp, fsp, flags, malloc) \
+ memp_stat((env), (gsp), (fsp), (malloc))
+#define LOG_ARCHIVE(env, listp, flags, malloc) \
+ log_archive((env), (listp), (flags), (malloc))
+#endif
+#endif
+/*
+ * The defult thread stacksize for nspr21 is 64k. For OSF, we require
+ * a larger stacksize as actual storage allocation is higher i.e
+ * pointers are allocated 8 bytes but lower 4 bytes are used.
+ * The value 0 means use the default stacksize.
+ */
+#if defined (OSF1) || defined (__LP64__) || defined (_LP64) /* 64-bit architectures need bigger stacks */
+#define DEFAULT_THREAD_STACKSIZE 131072L
+#else
+#define DEFAULT_THREAD_STACKSIZE 0
+#endif
+
+#ifdef _WIN32
+#define FILE_CREATE_MODE S_IREAD | S_IWRITE
+#define DIR_CREATE_MODE 0755
+#else /* _WIN32 */
+#define FILE_CREATE_MODE S_IRUSR | S_IWUSR
+#define DIR_CREATE_MODE 0755
+#endif
+
+#define NO_DISK_SPACE 1024
+#define MIN_DISK_SPACE 10485760 /* 10 MB */
+
+/***** Data Definitions *****/
+
+/* possible changelog open modes */
+typedef enum
+{
+ CL5_OPEN_NONE, /* nothing specified */
+ CL5_OPEN_NORMAL, /* open for normal read/write use */
+ CL5_OPEN_RESTORE_RECOVER, /* restore from archive and recover */
+ CL5_OPEN_RESTORE, /* restore, but no recovery */
+ CL5_OPEN_LDIF2CL, /* open as part of ldif2cl: no locking,
+ recovery, checkpointing */
+ CL5_OPEN_CLEAN_RECOVER /* remove env after recover open (upgrade) */
+} CL5OpenMode;
+
+#define DB_FILE_DELETED 0x1
+#define DB_FILE_INIT 0x2
+/* this structure represents one changelog file, Each changelog file contains
+ changes applied to a single backend. Files are named by the database id */
+typedef struct cl5dbfile
+{
+ char *name; /* file name (with the extension) */
+ char *replGen; /* replica generation of the data */
+ char *replName; /* replica name */
+ DB *db; /* db handle to the changelog file*/
+ int entryCount; /* number of entries in the file */
+ int flags; /* currently used to mark the file as deleted
+ * or as initialized */
+ RUV *purgeRUV; /* ruv to which the file has been purged */
+ RUV *maxRUV; /* ruv that marks the upper boundary of the data */
+ char *semaName; /* semaphore name */
+ PRSem *sema; /* semaphore for max concurrent cl writes */
+}CL5DBFile;
+
+/* structure that allows to iterate through entries to be sent to a consumer
+ that originated on a particular supplier. */
+struct cl5replayiterator
+{
+ Object *fileObj;
+ CLC_Buffer *clcache; /* changelog cache */
+ ReplicaId consumerRID; /* consumer's RID */
+ const RUV *consumerRuv; /* consumer's update vector */
+ Object *supplierRuvObj;/* supplier's update vector object */
+};
+
+typedef struct cl5iterator
+{
+ DBC *cursor; /* current position in the db file */
+ Object *file; /* handle to release db file object */
+}CL5Iterator;
+
+/* changelog trimming configuration */
+typedef struct cl5trim
+{
+ time_t maxAge; /* maximum entry age in seconds */
+ int maxEntries; /* maximum number of entries across all changelog files */
+ PRLock* lock; /* controls access to trimming configuration */
+} CL5Trim;
+
+/* this structure defines 5.0 changelog internals */
+typedef struct cl5desc
+{
+ char *dbDir; /* absolute path to changelog directory */
+ DB_ENV *dbEnv; /* db environment shared by all db files */
+ int dbEnvOpenFlags;/* openflag used for env->open */
+ Objset *dbFiles; /* ref counted set of changelog files (CL5DBFile) */
+ PRLock *fileLock; /* ensures that changelog file is not added twice */
+ CL5OpenMode dbOpenMode; /* how we open db */
+ CL5DBConfig dbConfig; /* database configuration params */
+ CL5Trim dbTrim; /* trimming parameters */
+ CL5State dbState; /* changelog current state */
+ PRRWLock *stLock; /* lock that controls access to the changelog state */
+ PRBool dbRmOnClose;/* indicates whether changelog should be removed when
+ it is closed */
+ PRBool fatalError; /* bad stuff happened like out of disk space; don't
+ write guardian file on close - UnUsed so far */
+ int threadCount;/* threads that globally access changelog like
+ deadlock detection, etc. */
+ PRLock *clLock; /* Lock associated to clVar, used to notify threads on close */
+ PRCondVar *clCvar; /* Condition Variable used to notify threads on close */
+} CL5Desc;
+
+typedef void (*VFP)(void *);
+
+int g_get_shutdown(); /* declared in proto-slap.h */
+
+/***** Global Variables *****/
+static CL5Desc s_cl5Desc;
+
+/***** Forward Declarations *****/
+
+/* changelog initialization and cleanup */
+static int _cl5Open (const char *dir, const CL5DBConfig *config, CL5OpenMode openMode);
+static int _cl5AppInit (PRBool *didRecovery);
+static int _cl5DBOpen ();
+static void _cl5SetDefaultDBConfig ();
+static void _cl5SetDBConfig (const CL5DBConfig *config);
+static void _cl5InitDBEnv(DB_ENV *dbEnv);
+static int _cl5CheckDBVersion ();
+static int _cl5ReadDBVersion (const char *dir, char *clVersion);
+static int _cl5WriteDBVersion ();
+static int _cl5CheckGuardian ();
+static int _cl5ReadGuardian (char *buff);
+static int _cl5WriteGuardian ();
+static int _cl5RemoveGuardian ();
+static void _cl5Close ();
+static int _cl5Delete (const char *dir, PRBool rmDir);
+static void _cl5DBClose ();
+
+/* thread management */
+static int _cl5DispatchDBThreads ();
+static int _cl5AddThread ();
+static void _cl5RemoveThread ();
+static int _cl5DeadlockMain (void *param);
+static int _cl5CheckpointMain (void *param);
+static int _cl5TrickleMain (void *param);
+
+/* functions that work with individual changelog files */
+static int _cl5NewDBFile (const char *replName, const char *replGen, CL5DBFile** dbFile);
+static int _cl5DBOpenFile (Object *replica, Object **obj, PRBool checkDups);
+static int _cl5DBOpenFileByReplicaName (const char *replName, const char *replGen,
+ Object **obj, PRBool checkDups);
+static void _cl5DBCloseFile (void **data);
+static void _cl5DBDeleteFile (Object *obj);
+static void _cl5DBFileInitialized (Object *obj);
+static int _cl5GetDBFile (Object *replica, Object **obj);
+static int _cl5GetDBFileByReplicaName (const char *replName, const char *replGen,
+ Object **obj);
+static int _cl5AddDBFile (CL5DBFile *file, Object **obj);
+static int _cl5CompareDBFile (Object *el1, const void *el2);
+static int _cl5CopyDBFiles (const char *srcDir, const char *distDir, Object **replicas);
+static char* _cl5Replica2FileName (Object *replica);
+static char* _cl5MakeFileName (const char *replName, const char *replGen);
+static PRBool _cl5FileName2Replica (const char *fileName, Object **replica);
+static int _cl5ExportFile (PRFileDesc *prFile, Object *obj);
+static PRBool _cl5ReplicaInList (Object *replica, Object **replicas);
+
+/* data storage and retrieval */
+static int _cl5Entry2DBData (const CL5Entry *entry, char **data, PRUint32 *len);
+static int _cl5WriteOperation(const char *replName, const char *replGen,
+ const slapi_operation_parameters *op, PRBool local);
+static int _cl5GetFirstEntry (Object *obj, CL5Entry *entry, void **iterator, DB_TXN *txnid);
+static int _cl5GetNextEntry (CL5Entry *entry, void *iterator);
+static int _cl5CurrentDeleteEntry (void *iterator);
+static PRBool _cl5IsValidIterator (const CL5Iterator *iterator);
+static int _cl5GetOperation (Object *replica, slapi_operation_parameters *op);
+static const char* _cl5OperationType2Str (int type);
+static int _cl5Str2OperationType (const char *str);
+static void _cl5WriteString (const char *str, char **buff);
+static void _cl5ReadString (char **str, char **buff);
+static void _cl5WriteMods (LDAPMod **mods, char **buff);
+static void _cl5WriteMod (LDAPMod *mod, char **buff);
+static int _cl5ReadMods (LDAPMod ***mods, char **buff);
+static int _cl5ReadMod (Slapi_Mod *mod, char **buff);
+static int _cl5GetModsSize (LDAPMod **mods);
+static int _cl5GetModSize (LDAPMod *mod);
+static void _cl5ReadBerval (struct berval *bv, char** buff);
+static void _cl5WriteBerval (struct berval *bv, char** buff);
+static int _cl5ReadBervals (struct berval ***bv, char** buff, unsigned int size);
+static int _cl5WriteBervals (struct berval **bv, char** buff, unsigned int *size);
+
+/* replay iteration */
+static PRBool _cl5ValidReplayIterator (const CL5ReplayIterator *iterator);
+static int _cl5PositionCursorForReplay (ReplicaId consumerRID, const RUV *consumerRuv,
+ Object *replica, Object *fileObject, CL5ReplayIterator **iterator);
+static int _cl5CheckMissingCSN (const CSN *minCsn, const RUV *supplierRUV, CL5DBFile *file);
+
+/* changelog trimming */
+static int _cl5TrimInit ();
+static void _cl5TrimCleanup ();
+static int _cl5TrimMain (void *param);
+static void _cl5DoTrimming ();
+static void _cl5TrimFile (Object *obj, long *numToTrim);
+static PRBool _cl5CanTrim (time_t time, long *numToTrim);
+static int _cl5ReadRUV (const char *replGen, Object *obj, PRBool purge);
+static int _cl5WriteRUV (CL5DBFile *file, PRBool purge);
+static int _cl5ConstructRUV (const char *replGen, Object *obj, PRBool purge);
+static int _cl5UpdateRUV (Object *obj, CSN *csn, PRBool newReplica, PRBool purge);
+static int _cl5GetRUV2Purge2 (Object *fileObj, RUV **ruv);
+
+/* db error processing */
+static void _cl5DBLogPrint(const char* prefix, char *buffer);
+
+/* bakup/recovery, import/export */
+static PRBool _cl5IsLogFile (const char *name);
+static int _cl5Recover (int open_flags, DB_ENV *dbEnv);
+static int _cl5LDIF2Operation (char *ldifEntry, slapi_operation_parameters *op,
+ char **replGen);
+static int _cl5Operation2LDIF (const slapi_operation_parameters *op, const char *replGen,
+ char **ldifEntry, PRInt32 *lenLDIF);
+
+/* entry count */
+static int _cl5GetEntryCount (CL5DBFile *file);
+static int _cl5WriteEntryCount (CL5DBFile *file);
+
+/* misc */
+static char* _cl5GetHelperEntryKey (int type, char *csnStr);
+static Object* _cl5GetReplica (const slapi_operation_parameters *op, const char* replGen);
+static int _cl5FileEndsWith(const char *filename, const char *ext);
+
+/* Callback function for libdb to spit error info into our log */
+static void dblayer_log_print(const char* prefix, char *buffer)
+{
+ /* We ignore the prefix since we know who we are anyway */
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "libdb: %s\n", buffer);
+}
+
+static PRLock *cl5_diskfull_lock = NULL;
+static int cl5_diskfull_flag = 0;
+
+static void cl5_set_diskfull();
+static void cl5_set_no_diskfull();
+
+/***** Module APIs *****/
+
+/* Name: cl5Init
+ Description: initializes changelog module; must be called by a single thread
+ before any other changelog function.
+ Parameters: none
+ Return: CL5_SUCCESS if function is successful;
+ CL5_SYSTEM_ERROR error if NSPR call fails.
+ */
+int cl5Init ()
+{
+ s_cl5Desc.stLock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "state_lock");
+ if (s_cl5Desc.stLock == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5Init: failed to create state lock; NSPR error - %d\n",
+ PR_GetError ());
+ return CL5_SYSTEM_ERROR;
+ }
+ if ((s_cl5Desc.clLock = PR_NewLock()) == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5Init: failed to create on close lock; NSPR error - %d\n",
+ PR_GetError ());
+ return CL5_SYSTEM_ERROR;
+
+ }
+ if ((s_cl5Desc.clCvar = PR_NewCondVar(s_cl5Desc.clLock)) == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5Init: failed to create on close cvar; NSPR error - %d\n",
+ PR_GetError ());
+ return CL5_SYSTEM_ERROR;
+ }
+
+ if (( clcache_init (&s_cl5Desc.dbEnv) != 0 )) {
+ return CL5_SYSTEM_ERROR;
+ }
+
+ s_cl5Desc.dbState = CL5_STATE_CLOSED;
+ s_cl5Desc.fatalError = PR_FALSE;
+ s_cl5Desc.dbRmOnClose = PR_FALSE;
+ s_cl5Desc.threadCount = 0;
+
+ if (NULL == cl5_diskfull_lock)
+ {
+ cl5_diskfull_lock = PR_NewLock ();
+ }
+
+ return CL5_SUCCESS;
+}
+
+/* Name: cl5Cleanup
+ Description: performs cleanup of the changelog module; must be called by a single
+ thread; it closes changelog if it is still open.
+ Parameters: none
+ Return: none
+ */
+void cl5Cleanup ()
+{
+ /* close db if it is still open */
+ if (s_cl5Desc.dbState == CL5_STATE_OPEN)
+ {
+ cl5Close ();
+ }
+
+ if (s_cl5Desc.stLock)
+ PR_DestroyRWLock (s_cl5Desc.stLock);
+ s_cl5Desc.stLock = NULL;
+
+ if (cl5_diskfull_lock)
+ {
+ PR_DestroyLock (cl5_diskfull_lock);
+ cl5_diskfull_lock = NULL;
+ }
+
+ memset (&s_cl5Desc, 0, sizeof (s_cl5Desc));
+}
+
+/* Name: cl5Open
+ Description: opens changelog; must be called after changelog is
+ initialized using cl5Init. It is thread safe and the second
+ call is ignored.
+ Parameters: dir - changelog dir
+ config - db configuration parameters; currently not used
+ Return: CL5_SUCCESS if successfull;
+ CL5_BAD_DATA if invalid directory is passed;
+ CL5_BAD_STATE if changelog is not initialized;
+ CL5_BAD_DBVERSION if dbversion file is missing or has unexpected data
+ CL5_SYSTEM_ERROR if NSPR error occured (during db directory creation);
+ CL5_MEMORY_ERROR if memory allocation fails;
+ CL5_DB_ERROR if db initialization fails.
+ */
+int cl5Open (const char *dir, const CL5DBConfig *config)
+{
+ int rc;
+
+ if (dir == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "cl5Open: null directory\n");
+ return CL5_BAD_DATA;
+ }
+
+ if (s_cl5Desc.dbState == CL5_STATE_NONE)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5Open: changelog is not initialized\n");
+ return CL5_BAD_STATE;
+ }
+
+ /* prevent state from changing */
+ PR_RWLock_Wlock (s_cl5Desc.stLock);
+
+ /* already open - ignore */
+ if (s_cl5Desc.dbState == CL5_STATE_OPEN)
+ {
+ slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl,
+ "cl5Open: changelog already opened; request ignored\n");
+ rc = CL5_SUCCESS;
+ goto done;
+ }
+ else if (s_cl5Desc.dbState != CL5_STATE_CLOSED)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5Open: invalid state - %d\n", s_cl5Desc.dbState);
+ rc = CL5_BAD_STATE;
+ goto done;
+ }
+
+ rc = _cl5Open (dir, config, CL5_OPEN_NORMAL);
+ if (rc != CL5_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5Open: failed to open changelog\n");
+ goto done;
+ }
+
+ /* dispatch global threads like deadlock detection, trimming, etc */
+ rc = _cl5DispatchDBThreads ();
+ if (rc != CL5_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5Open: failed to start database monitoring threads\n");
+
+ _cl5Close ();
+ }
+ else
+ {
+ s_cl5Desc.dbState = CL5_STATE_OPEN;
+ }
+
+done:;
+ PR_RWLock_Unlock (s_cl5Desc.stLock);
+
+ return rc;
+}
+
+/* Name: cl5Close
+ Description: closes changelog; waits until all threads are done using changelog;
+ call is ignored if changelog is already closed.
+ Parameters: none
+ Return: CL5_SUCCESS if successful;
+ CL5_BAD_STATE if db is not in the open or closed state;
+ CL5_SYSTEM_ERROR if NSPR call fails;
+ CL5_DB_ERROR if db shutdown fails
+ */
+int cl5Close ()
+{
+ int rc = CL5_SUCCESS;
+
+ if (s_cl5Desc.dbState == CL5_STATE_NONE)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "cl5Close: changelog is not initialized\n");
+ return CL5_BAD_STATE;
+ }
+
+ PR_RWLock_Wlock (s_cl5Desc.stLock);
+
+ /* already closed - ignore */
+ if (s_cl5Desc.dbState == CL5_STATE_CLOSED)
+ {
+ slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl,
+ "cl5Close: changelog closed; request ignored\n");
+ PR_RWLock_Unlock (s_cl5Desc.stLock);
+ return CL5_SUCCESS;
+ }
+ else if (s_cl5Desc.dbState != CL5_STATE_OPEN)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "cl5Close: invalid state - %d\n", s_cl5Desc.dbState);
+ PR_RWLock_Unlock (s_cl5Desc.stLock);
+ return CL5_BAD_STATE;
+ }
+
+ /* signal changelog closing to all threads */
+ s_cl5Desc.dbState = CL5_STATE_CLOSING;
+
+ PR_Lock(s_cl5Desc.clLock);
+ PR_NotifyCondVar(s_cl5Desc.clCvar);
+ PR_Unlock(s_cl5Desc.clLock);
+
+ _cl5Close ();
+
+ s_cl5Desc.dbState = CL5_STATE_CLOSED;
+
+ PR_RWLock_Unlock (s_cl5Desc.stLock);
+
+ return rc;
+}
+
+/* Name: cl5Delete
+ Description: removes changelog; changelog must be in the closed state.
+ Parameters: dir - changelog directory
+ Return: CL5_SUCCESS if successful;
+ CL5_BAD_STATE if the changelog is not in closed state;
+ CL5_BAD_DATA if invalid directory supplied
+ CL5_SYSTEM_ERROR if NSPR call fails
+ */
+int cl5Delete (const char *dir)
+{
+ int rc;
+
+ if (dir == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl, "cl5Delete: null directory\n");
+ return CL5_BAD_DATA;
+ }
+
+ if (s_cl5Desc.dbState == CL5_STATE_NONE)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "cl5Delete: changelog is not initialized\n");
+ return CL5_BAD_STATE;
+ }
+
+ PR_RWLock_Wlock (s_cl5Desc.stLock);
+
+ if (s_cl5Desc.dbState != CL5_STATE_CLOSED)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "cl5Delete: invalid state - %d\n", s_cl5Desc.dbState);
+ PR_RWLock_Unlock (s_cl5Desc.stLock);
+ return CL5_BAD_STATE;
+ }
+
+ rc = _cl5Delete (dir, PR_TRUE /* remove changelog dir */);
+ if (rc != CL5_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "cl5Delete: failed to remove changelog\n");
+ }
+
+ PR_RWLock_Unlock (s_cl5Desc.stLock);
+ return rc;
+}
+
+/* Name: cl5OpenDB
+ Description: opens changelog file for specified file
+ Parameters: replica - replica whose file we wish to open
+ Return: CL5_SUCCESS if successful;
+ CL5_BAD_STATE if the changelog is not initialized;
+ CL5_BAD_DATA - if NULL id is supplied
+ */
+int cl5OpenDB (Object *replica)
+{
+ int rc;
+
+ if (replica == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "cl5OpenDB: null replica\n");
+ return CL5_BAD_DATA;
+ }
+
+ if (s_cl5Desc.dbState == CL5_STATE_NONE)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "cl5OpenDB: changelog is not initialized\n");
+ return CL5_BAD_STATE;
+ }
+
+ /* make sure that changelog stays open while operation is in progress */
+ rc = _cl5AddThread ();
+ if (rc != CL5_SUCCESS)
+ return rc;
+
+ rc = _cl5DBOpenFile (replica, NULL /* file object */, PR_TRUE /* check for duplicates */);
+
+ _cl5RemoveThread ();
+
+ return rc;
+}
+
+/* Name: cl5CloseDB
+ Description: closes changelog file for the specified replica
+ Parameters: replica - replica whose file we wish to close
+ Return: CL5_SUCCESS if successful;
+ CL5_BAD_STATE if the changelog is not initialized;
+ CL5_BAD_DATA - if NULL id is supplied
+ CL5_NOTFOUND - nothing is known about specified database
+ */
+int cl5CloseDB (Object *replica)
+{
+ int rc;
+ Object *obj;
+
+ if (replica == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "cl5CloseDB: null replica\n");
+ return CL5_BAD_DATA;
+ }
+
+ if (s_cl5Desc.dbState == CL5_STATE_NONE)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "cl5CloseDB: changelog is not initialized\n");
+ return CL5_BAD_STATE;
+ }
+
+ /* make sure that changelog is open while operation is in progress */
+ rc = _cl5AddThread ();
+ if (rc != CL5_SUCCESS)
+ return rc;
+
+ rc = _cl5GetDBFile (replica, &obj);
+ if (rc == CL5_SUCCESS)
+ {
+ rc = objset_remove_obj(s_cl5Desc.dbFiles, obj);
+ object_release (obj);
+ }
+ else
+ {
+ Replica *r;
+
+ r = (Replica*)object_get_data (replica);
+ PR_ASSERT (r);
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "cl5CloseDB: failed to close file for replica at (%s)\n",
+ slapi_sdn_get_dn (replica_get_root (r)));
+ }
+
+ _cl5RemoveThread ();
+ return rc;
+}
+
+/* Name: cl5DeleteDB
+ Description: asynchronously removes changelog file for the specified replica.
+ The file is physically removed when it is no longer in use.
+ This function is called when a backend is removed or reloaded.
+ Parameters: replica - replica whose file we wish to delete
+ Return: CL5_SUCCESS if successful;
+ CL5_BAD_STATE if the changelog is not initialized;
+ CL5_BAD_DATA - if NULL id is supplied
+ CL5_NOTFOUND - nothing is known about specified database
+ */
+int cl5DeleteDB (Object *replica)
+{
+ Object *obj;
+ int rc;
+
+ if (replica == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "cl5DeleteDB: invalid database id\n");
+ return CL5_BAD_DATA;
+ }
+
+ /* changelog is not initialized */
+ if (s_cl5Desc.dbState == CL5_STATE_NONE)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "cl5DeleteDB: "
+ "changelog is not initialized\n");
+ return CL5_BAD_STATE;
+ }
+
+ /* make sure that changelog stays open while operation is in progress */
+ rc = _cl5AddThread ();
+ if (rc != CL5_SUCCESS)
+ return rc;
+
+ rc = _cl5GetDBFile (replica, &obj);
+ if (rc == CL5_SUCCESS)
+ {
+ _cl5DBDeleteFile (obj);
+ }
+ else
+ {
+ Replica *r = (Replica*)object_get_data (replica);
+ PR_ASSERT (r);
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "cl5DeleteDB: "
+ "file for replica at (%s) not found\n",
+ slapi_sdn_get_dn (replica_get_root (r)));
+ }
+
+ _cl5RemoveThread ();
+ return rc;
+}
+
+/* Name: cl5DeleteDBSync
+ Description: The same as cl5DeleteDB except the function does not return
+ until the file is removed.
+*/
+int cl5DeleteDBSync (Object *replica)
+{
+ Object *obj;
+ int rc;
+ CL5DBFile *file;
+ char fName [MAXPATHLEN + 1];
+
+ if (replica == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "cl5DeleteDBSync: invalid database id\n");
+ return CL5_BAD_DATA;
+ }
+
+ /* changelog is not initialized */
+ if (s_cl5Desc.dbState == CL5_STATE_NONE)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "cl5DeleteDBSync: "
+ "changelog is not initialized\n");
+ return CL5_BAD_STATE;
+ }
+
+ /* make sure that changelog stays open while operation is in progress */
+ rc = _cl5AddThread ();
+ if (rc != CL5_SUCCESS)
+ return rc;
+
+ rc = _cl5GetDBFile (replica, &obj);
+ if (rc == CL5_SUCCESS)
+ {
+ file = (CL5DBFile*)object_get_data (obj);
+ PR_ASSERT (file);
+
+ PR_snprintf (fName, MAXPATHLEN, "%s/%s", s_cl5Desc.dbDir, file->name);
+
+ _cl5DBDeleteFile (obj);
+
+ /* wait until the file is gone */
+ while (PR_Access (fName, PR_ACCESS_EXISTS) == PR_SUCCESS)
+ {
+ DS_Sleep (PR_MillisecondsToInterval(100));
+ }
+
+ }
+ else
+ {
+ Replica *r = (Replica*)object_get_data (replica);
+ PR_ASSERT (r);
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "cl5DeleteDBSync: "
+ "file for replica at (%s) not found\n",
+ slapi_sdn_get_dn (replica_get_root (r)));
+ }
+
+ _cl5RemoveThread ();
+ return rc;
+}
+
+/* Name: cl5GetUpperBoundRUV
+ Description: retrieves vector for that represnts the upper bound of the changes for a replica.
+ Parameters: r - replica for which the purge vector is requested
+ ruv - contains a copy of the purge ruv if function is successful;
+ unchanged otherwise. It is responsobility pf the caller to free
+ the ruv when it is no longer is in use
+ Return: CL5_SUCCESS if function is successfull
+ CL5_BAD_STATE if the changelog is not initialized;
+ CL5_BAD_DATA - if NULL id is supplied
+ CL5_NOTFOUND, if changelog file for replica is not found
+ */
+int cl5GetUpperBoundRUV (Replica *r, RUV **ruv)
+{
+ int rc;
+ Object *r_obj, *file_obj;
+ CL5DBFile *file;
+
+ if (r == NULL || ruv == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "cl5GetUpperBoundRUV: invalid parameters\n");
+ return CL5_BAD_DATA;
+ }
+
+ /* changelog is not initialized */
+ if (s_cl5Desc.dbState == CL5_STATE_NONE)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "cl5GetUpperBoundRUV: "
+ "changelog is not initialized\n");
+ return CL5_BAD_STATE;
+ }
+
+ /* make sure that changelog stays open while operation is in progress */
+ rc = _cl5AddThread ();
+ if (rc != CL5_SUCCESS)
+ return rc;
+
+ /* create a temporary replica object because of the interface we have */
+ r_obj = object_new (r, NULL);
+
+ rc = _cl5GetDBFile (r_obj, &file_obj);
+ if (rc == CL5_SUCCESS)
+ {
+ file = (CL5DBFile*)object_get_data (file_obj);
+ PR_ASSERT (file && file->maxRUV);
+
+ *ruv = ruv_dup (file->maxRUV);
+
+ object_release (file_obj);
+ }
+
+ object_release (r_obj);
+
+ _cl5RemoveThread ();
+ return rc;
+}
+
+/* Name: cl5Backup
+ Description: makes a backup of the changelog including *.db2,
+ log files, and dbversion. Can be called with the changelog in either open or
+ closed state.
+ Parameters: bkDir - directory to which the data is backed up;
+ created if it does not exist
+ replicas - optional list of replicas whose changes should be backed up;
+ if the list is NULL, entire changelog is backed up.
+ Return: CL5_SUCCESS if function is successful;
+ CL5_BAD_DATA if invalid directory is passed;
+ CL5_BAD_STATE if changelog has not been initialized;
+ CL5_DB_ERROR if db call fails;
+ CL5_SYSTEM_ERROR if NSPR call or file copy failes.
+ */
+int cl5Backup (const char *bkDir, Object **replicas)
+{
+ int rc;
+ char **list = NULL;
+ char **logFile;
+ char srcFile [MAXPATHLEN + 1];
+ char destFile[MAXPATHLEN + 1];
+ DB_TXN *txn = NULL;
+
+ if (bkDir == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "cl5Backup: null backup directory\n");
+ return CL5_BAD_DATA;
+ }
+
+ /* changelog must be initialized */
+ if (s_cl5Desc.dbState == CL5_STATE_NONE)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5Backup: changelog is not initialized\n");
+ return CL5_BAD_STATE;
+ }
+
+ /* make sure that changelog stays open while operation is in progress */
+ rc = _cl5AddThread ();
+ if (rc != CL5_SUCCESS)
+ return rc;
+
+ /* create backup directory if necessary */
+ rc = cl5CreateDirIfNeeded (bkDir);
+ if (rc != CL5_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5Backup: failed to create backup directory\n");
+ goto done;
+ }
+
+ /* start transaction to tempararily prevent transaction log
+ from being trimmed
+ */
+ rc = TXN_BEGIN(s_cl5Desc.dbEnv, NULL /*pid*/, &txn, 0);
+ if (rc != 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5Backup: failed to begin transaction; db error - %d %s\n",
+ rc, db_strerror(rc));
+ rc = CL5_DB_ERROR;
+ goto done;
+ }
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl,
+ "cl5Backup: starting changelog backup from %s to %s ...\n", s_cl5Desc.dbDir, bkDir);
+
+ /* The following files are backed up: *.<dbext>, log files, dbversion file */
+
+ /* copy db file */
+ /* ONREPL currently, list of replicas is ignored because db code can't handle
+ discrepancy between transaction log and present files; should be fixed before 5.0 ships */
+ rc = _cl5CopyDBFiles (s_cl5Desc.dbDir, bkDir, replicas);
+ if (rc != CL5_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5Backup : failed to copy database files from %s to %s\n", s_cl5Desc.dbDir, bkDir);
+ goto done;
+ }
+
+ /* copy db log files */
+ rc = LOG_ARCHIVE(s_cl5Desc.dbEnv, &list, DB_ARCH_LOG, malloc);
+ if (rc != 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5Backup: failed to get list of log files; db error - %d %s\n",
+ rc, db_strerror(rc));
+ rc = CL5_SYSTEM_ERROR;
+ goto done;
+ }
+
+ if (list)
+ {
+ logFile = list;
+ while (*logFile)
+ {
+ PR_snprintf(srcFile, MAXPATHLEN, "%s/%s", s_cl5Desc.dbDir, *logFile);
+ PR_snprintf(destFile, MAXPATHLEN, "%s/%s", bkDir, *logFile);
+ rc = copyfile(srcFile, destFile, 0, FILE_CREATE_MODE);
+ if (rc != 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5Backup: failed to copy %s\n", *logFile);
+ rc = CL5_SYSTEM_ERROR;
+ goto done;
+ }
+
+ logFile ++;
+ }
+
+ free(list);
+ }
+
+ /* now, copy the version file */
+ PR_snprintf(srcFile, MAXPATHLEN, "%s/%s", s_cl5Desc.dbDir, VERSION_FILE);
+ PR_snprintf(destFile, MAXPATHLEN, "%s/%s", bkDir, VERSION_FILE);
+ rc = copyfile(srcFile, destFile, 0, FILE_CREATE_MODE);
+ if (rc != 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5Backup: failed to copy %s\n", VERSION_FILE);
+ rc = CL5_SYSTEM_ERROR;
+ goto done;
+ }
+
+ rc = CL5_SUCCESS;
+ slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl,
+ "cl5Backup: changelog backup is finished \n");
+done:;
+ if (txn && TXN_ABORT (txn) != 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5Backup: failed to abort transaction; db error - %d %s\n",
+ rc, db_strerror(rc));
+ rc = CL5_DB_ERROR;
+ }
+
+ _cl5RemoveThread ();
+
+ return rc;
+}
+
+/* Name: cl5Restore
+ Description: restores changelog from the backed up copy. Changelog must be ibnitalized and closed.
+ Parameters: clDir - changelog dir
+ bkDir - directory that contains the backup
+ replicas - optional list of replicas whose changes should be recovered;
+ if the list is NULL, entire changelog is recovered.
+ Return: CL5_SUCCESS if function is successfull;
+ CL5_BAD_DATA if invalid parameter is passed;
+ CL5_BAD_STATE if changelog is open or not initialized;
+ CL5_DB_ERROR if db call fails;
+ CL5_SYSTEM_ERROR if NSPR call of file copy fails
+ */
+int cl5Restore (const char *clDir, const char *bkDir, Object **replicas)
+{
+ int rc;
+ char srcFile[MAXPATHLEN + 1];
+ char destFile[MAXPATHLEN + 1];
+ PRDir *prDir;
+ PRDirEntry *prDirEntry;
+ int seenLog = 0; /* Tells us if we restored any logfiles */
+
+ if (clDir == NULL || bkDir == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "cl5Restore: null parameter\n");
+ return CL5_BAD_DATA;
+ }
+
+ if (s_cl5Desc.dbState == CL5_STATE_NONE)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5Restore: changelog is not initialized\n");
+ return CL5_BAD_STATE;
+ }
+
+ /* prevent state change while recovery is in progress */
+ PR_RWLock_Wlock (s_cl5Desc.stLock);
+
+ if (s_cl5Desc.dbState != CL5_STATE_CLOSED)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5Restore: changelog must be closed\n");
+ PR_RWLock_Unlock (s_cl5Desc.stLock);
+ return CL5_BAD_STATE;
+ }
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl,
+ "cl5Restore: starting changelog recovery from %s to %s ...\n", bkDir, clDir);
+
+ /* delete current changelog content */
+ rc = _cl5Delete (clDir, PR_FALSE);
+ if (rc != CL5_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5Restore: failed to remove changelog\n");
+ goto done;
+ }
+
+ /* We copy the files over from the staging area */
+ prDir = PR_OpenDir(bkDir);
+ if (prDir == NULL)
+ {
+ rc = CL5_SYSTEM_ERROR;
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5Restore: unable to access backup directory %s; NSPR error - %d\n",
+ bkDir, PR_GetError ());
+ goto done;
+ }
+
+ while (NULL != (prDirEntry = PR_ReadDir(prDir, PR_SKIP_DOT | PR_SKIP_DOT_DOT)))
+ {
+ if (NULL == prDirEntry->name) /* NSPR doesn't behave like the docs say it should */
+ {
+ break;
+ }
+
+ /* Log files have names of the form "log.xxxxx". We detect these by looking for
+ the prefix "log." and the lack of the ".<dbext>" suffix */
+ seenLog |= _cl5IsLogFile(prDirEntry->name);
+
+ /* ONREPL currently, list of replicas is ignored because db code can't handle discrepancy
+ between transaction log and present files; this should change before 5.0 ships */
+ PR_snprintf(destFile, MAXPATHLEN, "%s/%s", clDir, prDirEntry->name);
+ PR_snprintf(srcFile, MAXPATHLEN, "%s/%s", bkDir, prDirEntry->name);
+ rc = copyfile(srcFile, destFile, 0, FILE_CREATE_MODE);
+ if (rc != 0)
+ {
+ rc = CL5_SYSTEM_ERROR;
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5Restore: failed to copy %s\n", prDirEntry->name);
+ PR_CloseDir(prDir);
+ goto done;
+ }
+ }
+
+ PR_CloseDir(prDir);
+
+ /* now open and close changelog to create all necessary files */
+ if (seenLog)
+ rc = _cl5Open (clDir, NULL, CL5_OPEN_RESTORE_RECOVER);
+ else
+ rc = _cl5Open (clDir, NULL, CL5_OPEN_RESTORE);
+
+ if (rc == CL5_SUCCESS)
+ {
+ _cl5Close ();
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl,
+ "cl5Restore: changelog recovery is finished \n");
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5Restore: failed open changelog after recovery\n");
+ }
+
+done:;
+ PR_RWLock_Unlock (s_cl5Desc.stLock);
+ return rc;
+}
+
+/* Name: cl5ExportLDIF
+ Description: dumps changelog to an LDIF file; changelog can be open or closed.
+ Parameters: clDir - changelog dir
+ ldifFile - full path to ldif file to write
+ replicas - optional list of replicas whose changes should be exported;
+ if the list is NULL, entire changelog is exported.
+ Return: CL5_SUCCESS if function is successfull;
+ CL5_BAD_DATA if invalid parameter is passed;
+ CL5_BAD_STATE if changelog is not initialized;
+ CL5_DB_ERROR if db api fails;
+ CL5_SYSTEM_ERROR if NSPR call fails;
+ CL5_MEMORY_ERROR if memory allocation fials.
+ */
+int cl5ExportLDIF (const char *ldifFile, Object **replicas)
+{
+ int i;
+ int rc;
+ PRFileDesc *prFile = NULL;
+ Object *obj;
+
+ if (ldifFile == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5ExportLDIF: null ldif file name\n");
+ return CL5_BAD_DATA;
+ }
+
+ if (s_cl5Desc.dbState == CL5_STATE_NONE)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5ExportLDIF: changelog is not initialized\n");
+ return CL5_BAD_STATE;
+ }
+
+ /* make sure that changelog is open while operation is in progress */
+ rc = _cl5AddThread ();
+ if (rc != CL5_SUCCESS)
+ return rc;
+
+ prFile = PR_Open (ldifFile, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 0600);
+ if (prFile == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5ExportLDIF: failed to open (%s) file; NSPR error - %d\n",
+ ldifFile, PR_GetError ());
+ rc = CL5_SYSTEM_ERROR;
+ goto done;
+ }
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl,
+ "cl5ExportLDIF: starting changelog export to (%s) ...\n", ldifFile);
+
+ if (replicas) /* export only selected files */
+ {
+ for (i = 0; replicas[i]; i++)
+ {
+ rc = _cl5GetDBFile (replicas[i], &obj);
+ if (rc == CL5_SUCCESS)
+ {
+ rc = _cl5ExportFile (prFile, obj);
+ object_release (obj);
+ }
+ else
+ {
+ Replica *r = (Replica*)object_get_data (replicas[i]);
+
+ PR_ASSERT (r);
+
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "cl5ExportLDIF: "
+ "failed to locate changelog file for replica at (%s)\n",
+ slapi_sdn_get_dn (replica_get_root (r)));
+ }
+ }
+ }
+ else /* export all files */
+ {
+ for (obj = objset_first_obj(s_cl5Desc.dbFiles); obj;
+ obj = objset_next_obj(s_cl5Desc.dbFiles, obj))
+ {
+ rc = _cl5ExportFile (prFile, obj);
+ object_release (obj);
+ }
+ }
+
+ rc = CL5_SUCCESS;
+done:;
+
+ _cl5RemoveThread ();
+
+ if (rc == CL5_SUCCESS)
+ slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl,
+ "cl5ExportLDIF: changelog export is finished.\n");
+
+ if (prFile)
+ PR_Close (prFile);
+
+ return rc;
+}
+
+/* Name: cl5ImportLDIF
+ Description: imports ldif file into changelog; changelog must be in the closed state
+ Parameters: clDir - changelog dir
+ ldifFile - absolute path to the ldif file to import
+ replicas - optional list of replicas whose data should be imported;
+ if the list is NULL, all data in the file is imported.
+ Return: CL5_SUCCESS if function is successfull;
+ CL5_BAD_DATA if invalid parameter is passed;
+ CL5_BAD_STATE if changelog is open or not inititalized;
+ CL5_DB_ERROR if db api fails;
+ CL5_SYSTEM_ERROR if NSPR call fails;
+ CL5_MEMORY_ERROR if memory allocation fials.
+ */
+int cl5ImportLDIF (const char *clDir, const char *ldifFile, Object **replicas)
+{
+ FILE *file;
+ int rc;
+ char *buff;
+ int lineno = 0;
+ slapi_operation_parameters op;
+ Object *replica = NULL;
+ char *replGen = NULL;
+
+ /* validate params */
+ if (ldifFile == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5ImportLDIF: null ldif file name\n");
+ return CL5_BAD_DATA;
+ }
+
+ if (s_cl5Desc.dbState == CL5_STATE_NONE)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5ImportLDIF: changelog is not initialized\n");
+ return CL5_BAD_STATE;
+ }
+
+ /* make sure that nobody change changelog state while import is in progress */
+ PR_RWLock_Wlock (s_cl5Desc.stLock);
+
+ /* make sure changelog is closed */
+ if (s_cl5Desc.dbState != CL5_STATE_CLOSED)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5ImportLDIF: invalid state - %d \n", s_cl5Desc.dbState);
+
+ PR_RWLock_Unlock (s_cl5Desc.stLock);
+ return CL5_BAD_STATE;
+ }
+
+ /* open LDIF file */
+ file = fopen (ldifFile, "r"); /* XXXggood Does fopen reliably work if > 255 files open? */
+ if (file == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5ImportLDIF: failed to open (%s) ldif file; system error - %d\n",
+ ldifFile, errno);
+ rc = CL5_SYSTEM_ERROR;
+ goto done;
+ }
+
+ /* remove changelog */
+ rc = _cl5Delete (clDir, PR_FALSE);
+ if (rc != CL5_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5ImportLDIF: failed to remove changelog\n");
+ goto done;
+ }
+
+ /* open changelog */
+ rc = _cl5Open (clDir, NULL, CL5_OPEN_LDIF2CL);
+ if (rc != CL5_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5ImportLDIF: failed to open changelog\n");
+ goto done;
+ }
+
+ /* read entries and write them to changelog */
+ while ((buff = ldif_get_entry( file, &lineno )) != NULL)
+ {
+ rc = _cl5LDIF2Operation (buff, &op, &replGen);
+ slapi_ch_free ((void**)&buff);
+ if (rc != CL5_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5ImportLDIF: failed to convert LDIF fragment to LDAP operation; "
+ "end of fragment line number - %d\n", lineno);
+ goto done;
+ }
+
+ /* if we perform selective import, check if the operation should be wriiten to changelog */
+ replica = _cl5GetReplica (&op, replGen);
+ if (replica == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5ImportLDIF: failed to locate replica for target dn (%s) and "
+ "replica generation %s\n", op.target_address.dn, replGen);
+
+ slapi_ch_free ((void**)&replGen);
+ operation_parameters_done (&op);
+ goto done;
+ }
+
+ if (!replicas || _cl5ReplicaInList (replica, replicas))
+ {
+ /* write operation creates the file if it does not exist */
+ rc = _cl5WriteOperation (replica_get_name ((Replica*)object_get_data(replica)),
+ replGen, &op, 1);
+ if (rc != CL5_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5ImportLDIF: failed to write operation to the changelog\n");
+ object_release (replica);
+ slapi_ch_free ((void**)&replGen);
+ operation_parameters_done (&op);
+ goto done;
+ }
+ }
+
+ object_release (replica);
+ slapi_ch_free ((void**)&replGen);
+ operation_parameters_done (&op);
+ }
+
+done:;
+ _cl5Close ();
+ PR_RWLock_Unlock (s_cl5Desc.stLock);
+ return rc;
+}
+
+/* Name: cl5GetState
+ Description: returns database state
+ Parameters: none
+ Return: changelog state
+ */
+int cl5GetState ()
+{
+ return s_cl5Desc.dbState;
+}
+
+/* Name: cl5ConfigTrimming
+ Description: sets changelog trimming parameters; changelog must be open.
+ Parameters: maxEntries - maximum number of entries in the chnagelog (in all files);
+ maxAge - maximum entry age;
+ Return: CL5_SUCCESS if successful;
+ CL5_BAD_STATE if changelog is not open
+ */
+int cl5ConfigTrimming (int maxEntries, const char *maxAge)
+{
+ if (s_cl5Desc.dbState == CL5_STATE_NONE)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5ConfigTrimming: changelog is not initialized\n");
+ return CL5_BAD_STATE;
+ }
+
+ /* make sure changelog is not closed while trimming configuration
+ is updated.*/
+ _cl5AddThread ();
+
+ PR_Lock (s_cl5Desc.dbTrim.lock);
+
+ if (maxAge)
+ {
+ /* don't ignore this argument */
+ if (strcmp (maxAge, CL5_STR_IGNORE) != 0)
+ {
+ s_cl5Desc.dbTrim.maxAge = age_str2time (maxAge);
+ }
+ }
+ else
+ {
+ /* unlimited */
+ s_cl5Desc.dbTrim.maxAge = 0;
+ }
+
+ if (maxEntries != CL5_NUM_IGNORE)
+ {
+ s_cl5Desc.dbTrim.maxEntries = maxEntries;
+ }
+
+ PR_Unlock (s_cl5Desc.dbTrim.lock);
+
+ _cl5RemoveThread ();
+
+ return CL5_SUCCESS;
+}
+
+/* Name: cl5GetOperation
+ Description: retireves operation specified by its csn and databaseid
+ Parameters: op - must contain csn and databaseid; the rest of data is
+ filled if function is successfull
+ Return: CL5_SUCCESS if function is successfull;
+ CL5_BAD_DATA if invalid op is passed;
+ CL5_BAD_STATE if db has not been initialized;
+ CL5_NOTFOUND if entry was not found;
+ CL5_DB_ERROR if any other db error occured;
+ CL5_BADFORMAT if db data format does not match entry format.
+ */
+int cl5GetOperation (Object *replica, slapi_operation_parameters *op)
+{
+ int rc;
+ char *agmt_name;
+
+ agmt_name = get_thread_private_agmtname();
+
+ if (replica == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "cl5GetOperation: NULL replica\n");
+ return CL5_BAD_DATA;
+ }
+
+ if (op == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "cl5GetOperation: NULL operation\n");
+ return CL5_BAD_DATA;
+ }
+
+ if (op->csn == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "%s: cl5GetOperation: operation contains no CSN\n", agmt_name);
+ return CL5_BAD_DATA;
+ }
+
+ if (s_cl5Desc.dbState == CL5_STATE_NONE)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "%s: cl5GetOperation: changelog is not initialized\n", agmt_name);
+ return CL5_BAD_STATE;
+ }
+
+ /* make sure that changelog is open while operation is in progress */
+ rc = _cl5AddThread ();
+ if (rc != CL5_SUCCESS)
+ return rc;
+
+ rc = _cl5GetOperation (replica, op);
+
+ _cl5RemoveThread ();
+
+ return rc;
+}
+
+/* Name: cl5GetFirstOperation
+ Description: retrieves first operation for a particular database
+ replica - replica for which the operation should be retrieved.
+ Parameters: op - buffer to store the operation;
+ iterator - to be passed to the call to cl5GetNextOperation
+ Return: CL5_SUCCESS, if successful
+ CL5_BADDATA, if operation is NULL
+ CL5_BAD_STATE, if changelog is not open
+ CL5_DB_ERROR, if db call fails
+ */
+int cl5GetFirstOperation (Object *replica, slapi_operation_parameters *op, void **iterator)
+{
+ int rc;
+ CL5Entry entry;
+ Object *obj;
+ char *agmt_name;
+
+ if (replica == NULL || op == NULL || iterator == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "cl5GetFirstOperation: invalid argument\n");
+ return CL5_BAD_DATA;
+ }
+
+ *iterator = NULL;
+
+ if (s_cl5Desc.dbState == CL5_STATE_NONE)
+ {
+ agmt_name = get_thread_private_agmtname();
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "%s: cl5GetFirstOperation: changelog is not initialized\n", agmt_name);
+ return CL5_BAD_STATE;
+ }
+
+ /* make sure that changelog stays open while operation is in progress */
+ rc = _cl5AddThread ();
+ if (rc != CL5_SUCCESS)
+ return rc;
+
+ rc = _cl5GetDBFile (replica, &obj);
+ if (rc != CL5_SUCCESS)
+ {
+ _cl5RemoveThread ();
+ return rc;
+ }
+
+ entry.op = op;
+ /* Callers of this function should cl5_operation_parameters_done(op) */
+ rc = _cl5GetFirstEntry (obj, &entry, iterator, NULL);
+ object_release (obj);
+
+ _cl5RemoveThread ();
+
+ return rc;
+}
+
+/* Name: cl5GetNextOperation
+ Description: retrieves the next op from the changelog as defined by the iterator;
+ changelog must be open.
+ Parameters: op - returned operation, if function is successful
+ iterator - in: identifies op to retrieve; out: identifies next op
+ Return: CL5_SUCCESS, if successful
+ CL5_BADDATA, if op is NULL
+ CL5_BAD_STATE, if changelog is not open
+ CL5_NOTFOUND, empty changelog
+ CL5_DB_ERROR, if db call fails
+ */
+int cl5GetNextOperation (slapi_operation_parameters *op, void *iterator)
+{
+ CL5Entry entry;
+
+ if (op == NULL || iterator == NULL || !_cl5IsValidIterator (iterator))
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "cl5GetNextOperation: invalid argument\n");
+ return CL5_BAD_DATA;
+ }
+
+ if (s_cl5Desc.dbState != CL5_STATE_OPEN)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "cl5GetNextOperation: changelog is not open\n");
+ return CL5_BAD_STATE;
+ }
+
+ /* we don't need to increment thread count since cl5GetFirstOperation
+ locked the file through which we are iterating */
+ entry.op = op;
+ /* Callers of this function should cl5_operation_parameters_done(op) */
+ return _cl5GetNextEntry (&entry, iterator);
+}
+
+/* Name: cl5DestroyIterator
+ Description: destroys iterator once iteration through changelog is done
+ Parameters: iterator - iterator to destroy
+ Return: none
+ */
+void cl5DestroyIterator (void *iterator)
+{
+ CL5Iterator *it = (CL5Iterator*)iterator;
+
+ if (it == NULL)
+ return;
+
+ /* close cursor */
+ if (it->cursor)
+ it->cursor->c_close (it->cursor);
+
+ if (it->file)
+ object_release (it->file);
+
+ slapi_ch_free ((void**)&it);
+}
+
+/* Name: cl5WriteOperation
+ Description: writes operation to changelog
+ Parameters: replName - name of the replica to which operation applies
+ replGen - replica generation for the operation
+ !!!Note that we pass name and generation rather than
+ replica object since generation can change while operation
+ is in progress (if the data is reloaded). !!!
+ op - operation to write
+ local - this is a non-replicated operation
+ Return: CL5_SUCCESS if function is successfull;
+ CL5_BAD_DATA if invalid op is passed;
+ CL5_BAD_STATE if db has not been initialized;
+ CL5_MEMORY_ERROR if memory allocation failed;
+ CL5_DB_ERROR if any other db error occured;
+ */
+int cl5WriteOperation(const char *replName, const char *replGen,
+ const slapi_operation_parameters *op, PRBool local)
+{
+ int rc;
+
+ if (op == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "cl5WriteOperation: NULL operation passed\n");
+ return CL5_BAD_DATA;
+ }
+
+ if (!IsValidOperation (op))
+ {
+ return CL5_BAD_DATA;
+ }
+
+
+ if (s_cl5Desc.dbState == CL5_STATE_NONE)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "cl5WriteOperation: changelog is not initialized\n");
+ return CL5_BAD_STATE;
+ }
+
+ /* make sure that changelog is open while operation is in progress */
+ rc = _cl5AddThread ();
+ if (rc != CL5_SUCCESS)
+ return rc;
+
+ rc = _cl5WriteOperation(replName, replGen, op, local);
+
+ /* update the upper bound ruv vector */
+ if (rc == CL5_SUCCESS)
+ {
+ Object *file_obj = NULL;
+
+ if ( _cl5GetDBFileByReplicaName (replName, replGen, &file_obj) == CL5_SUCCESS) {
+ rc = _cl5UpdateRUV (file_obj, op->csn, PR_FALSE, PR_FALSE);
+ object_release (file_obj);
+ }
+
+ }
+
+ _cl5RemoveThread ();
+
+ return rc;
+}
+
+/* Name: cl5CreateReplayIterator
+ Description: creates an iterator that allows to retireve changes that should
+ to be sent to the consumer identified by ruv. The iteration is peformed by
+ repeated calls to cl5GetNextOperationToReplay.
+ Parameters: replica - replica whose data we wish to iterate;
+ ruv - consumer ruv;
+ iterator - iterator to be passed to cl5GetNextOperationToReplay call
+ Return: CL5_SUCCESS, if function is successfull;
+ CL5_MISSING_DATA, if data that should be in the changelog is missing
+ CL5_PURGED_DATA, if some data that consumer needs has been purged.
+ Note that the iterator can be non null if the supplier contains
+ some data that needs to be sent to the consumer
+ CL5_NOTFOUND if the consumer is up to data with respect to the supplier
+ CL5_BAD_DATA if invalid parameter is passed;
+ CL5_BAD_STATE if db has not been open;
+ CL5_DB_ERROR if any other db error occured;
+ CL5_MEMORY_ERROR if memory allocation fails.
+ Algorithm: Build a list of csns from consumer's and supplier's ruv. For each element
+ of the consumer's ruv put max csn into the csn list. For each element
+ of the supplier's ruv not in the consumer's ruv put min csn from the
+ supplier's ruv into the list. The list contains, for each known replica,
+ the starting point for changes to be sent to the consumer.
+ Sort the list in accending order.
+ Build a hash which contains, for each known replica, whether the
+ supplier can bring the consumer up to data with respect to that replica.
+ The hash is used to decide whether a change can be sent to the consumer
+ Find the replica with the smallest csn in the list for which
+ we can bring the consumer up to date.
+ Position the db cursor on the change entry that corresponds to this csn.
+ Hash entries are created for each replica traversed so far. sendChanges
+ flag is set to FALSE for all repolicas except the last traversed.
+
+ */
+int cl5CreateReplayIterator (Private_Repl_Protocol *prp, const RUV *consumerRuv,
+ CL5ReplayIterator **iterator)
+{
+ int rc;
+ Object *replica;
+ Object *obj = NULL;
+
+ replica = prp->replica_object;
+ if (replica == NULL || consumerRuv == NULL || iterator == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "cl5CreateReplayIterator: invalid parameter\n");
+ return CL5_BAD_DATA;
+ }
+
+ *iterator = NULL;
+
+ if (s_cl5Desc.dbState == CL5_STATE_NONE)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "cl5CreateReplayIterator: changelog is not initialized\n");
+ return CL5_BAD_STATE;
+ }
+
+ /* make sure that changelog is open while operation is in progress */
+ rc = _cl5AddThread ();
+ if (rc != CL5_SUCCESS ) return rc;
+
+
+ rc = _cl5GetDBFile (replica, &obj);
+ if (rc == CL5_SUCCESS)
+ {
+ /* iterate through the ruv in csn order to find first master for which
+ we can replay changes */
+ ReplicaId consumerRID = agmt_get_consumer_rid ( prp->agmt, prp->conn );
+ rc = _cl5PositionCursorForReplay (consumerRID, consumerRuv, replica, obj, iterator);
+ if (rc != CL5_SUCCESS)
+ {
+ if (obj)
+ object_release (obj);
+ }
+ }
+
+ _cl5RemoveThread ();
+
+ return rc;
+}
+
+/* Name: cl5GetNextOperationToReplay
+ Description: retrieves next operation to be sent to a particular consumer and
+ that was created on a particular master. Consumer and master info
+ is encoded in the iterator parameter that must be created by call
+ to cl5CreateReplayIterator.
+ Parameters: iterator - iterator that identifies next entry to retrieve;
+ op - operation retrieved if function is successful
+ Return: CL5_SUCCESS if function is successfull;
+ CL5_BAD_DATA if invalid parameter is passed;
+ CL5_NOTFOUND if end of iteration list is reached
+ CL5_DB_ERROR if any other db error occured;
+ CL5_BADFORMAT if data in db is of unrecognized format;
+ CL5_MEMORY_ERROR if memory allocation fails.
+ Algorithm: Iterate through changelog entries until a change is found that
+ originated at the replica for which we are sending changes
+ (based on the information in the iteration hash) and
+ whose csn is larger than the csn already seen by the consumer
+ If change originated at the replica not in the hash,
+ determine whether we should send changes originated at the replica
+ and add replica entry into the hash. We can send the changes for
+ the replica if the current csn is smaller or equal to the csn
+ in the consumer's ruv (if present) or if it is equal to the min
+ csn in the supplier's ruv.
+ */
+int
+cl5GetNextOperationToReplay (CL5ReplayIterator *iterator, CL5Entry *entry)
+{
+ CSN *csn;
+ char *key, *data;
+ size_t keylen, datalen;
+ char *agmt_name;
+ int rc = 0;
+
+ agmt_name = get_thread_private_agmtname();
+
+ if (entry == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "%s: cl5GetNextOperationToReplay: invalid parameter passed\n", agmt_name);
+ return CL5_BAD_DATA;
+ }
+
+ rc = clcache_get_next_change (iterator->clcache, (void **)&key, &keylen, (void **)&data, &datalen, &csn);
+
+ if (rc == DB_NOTFOUND)
+ {
+ /*
+ * Abort means we've figured out that we've passed the replica Min CSN,
+ * so we should stop looping through the changelog
+ */
+ return CL5_NOTFOUND;
+ }
+
+ if (rc != 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, NULL, "%s: cl5GetNextOperationToReplay: "
+ "failed to read next entry; DB error %d\n", agmt_name, rc);
+ return CL5_DB_ERROR;
+ }
+
+ /* there is an entry we should return */
+ /* Callers of this function should cl5_operation_parameters_done(op) */
+ if ( 0 != cl5DBData2Entry ( data, datalen, entry ) )
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "%s: cl5GetNextOperationToReplay: failed to format entry rc=%d\n", agmt_name, rc);
+ return rc;
+ }
+
+ return CL5_SUCCESS;
+}
+
+/* Name: cl5DestroyReplayIterator
+ Description: destorys iterator
+ Parameters: iterator - iterator to destory
+ Return: none
+ */
+void cl5DestroyReplayIterator (CL5ReplayIterator **iterator)
+{
+ if (iterator == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "cl5DestroyReplayIterator: invalid iterartor passed\n");
+ return;
+ }
+
+ clcache_return_buffer ( &(*iterator)->clcache );
+
+ if ((*iterator)->fileObj)
+ object_release ((*iterator)->fileObj);
+
+ /* release supplier's ruv */
+ if ((*iterator)->supplierRuvObj)
+ object_release ((*iterator)->supplierRuvObj);
+
+ slapi_ch_free ((void **)iterator);
+}
+
+/* Name: cl5DeleteOnClose
+ Description: marks changelog for deletion when it is closed
+ Parameters: flag; if flag = 1 then delete else don't
+ Return: none
+ */
+void cl5DeleteOnClose (PRBool rm)
+{
+ s_cl5Desc.dbRmOnClose = rm;
+}
+
+/* Name: cl5GetDir
+ Description: returns changelog directory
+ Parameters: none
+ Return: copy of the directory; caller needs to free the string
+ */
+ char *cl5GetDir ()
+{
+ if (s_cl5Desc.dbDir == NULL)
+ {
+ return NULL;
+ }
+ else
+ {
+ return slapi_ch_strdup (s_cl5Desc.dbDir);
+ }
+}
+
+/* Name: cl5Exist
+ Description: checks if a changelog exists in the specified directory;
+ We consider changelog to exist if it contains the dbversion file.
+ Parameters: clDir - directory to check
+ Return: 1 - if changelog exists; 0 - otherwise
+ */
+PRBool cl5Exist (const char *clDir)
+{
+ char fName [MAXPATHLEN + 1];
+ int rc;
+
+ PR_snprintf (fName, MAXPATHLEN, "%s/%s", clDir, VERSION_FILE);
+ rc = PR_Access (fName, PR_ACCESS_EXISTS);
+
+ return (rc == PR_SUCCESS);
+}
+
+/* Name: cl5GetOperationCount
+ Description: returns number of entries in the changelog. The changelog must be
+ open for the value to be meaningful.
+ Parameters: replica - optional parameter that specifies the replica whose operations
+ we wish to count; if NULL all changelog entries are counted
+ Return: number of entries in the changelog
+ */
+
+int cl5GetOperationCount (Object *replica)
+{
+ Object *obj;
+ CL5DBFile *file;
+ int count = 0;
+ int rc;
+
+ if (s_cl5Desc.dbState == CL5_STATE_NONE)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "cl5GetOperationCount: changelog is not initialized\n");
+ return -1;
+ }
+
+ /* make sure that changelog is open while operation is in progress */
+ rc = _cl5AddThread ();
+ if (rc != CL5_SUCCESS)
+ return -1;
+
+ if (replica == NULL) /* compute total entry count */
+ {
+ obj = objset_first_obj (s_cl5Desc.dbFiles);
+ while (obj)
+ {
+ file = (CL5DBFile*)object_get_data (obj);
+ PR_ASSERT (file);
+ count += file->entryCount;
+ obj = objset_next_obj (s_cl5Desc.dbFiles, obj);
+ }
+ }
+ else /* return count for particular db */
+ {
+ /* select correct db file */
+ rc = _cl5GetDBFile (replica, &obj);
+ if (rc == CL5_SUCCESS)
+ {
+ file = (CL5DBFile*)object_get_data (obj);
+ PR_ASSERT (file);
+
+ count = file->entryCount;
+ object_release (obj);
+ }
+ else
+ {
+ count = 0;
+ }
+ }
+
+ _cl5RemoveThread ();
+ return count;
+}
+
+/***** Helper Functions *****/
+
+/* this call happens under state lock */
+static int _cl5Open (const char *dir, const CL5DBConfig *config, CL5OpenMode openMode)
+{
+ int rc;
+ PRBool didRecovery;
+
+ PR_ASSERT (dir);
+
+ /* setup db configuration parameters */
+ if (config)
+ {
+ _cl5SetDBConfig (config);
+ }
+ else
+ {
+ _cl5SetDefaultDBConfig ();
+ }
+
+ /* initialize trimming */
+ rc = _cl5TrimInit ();
+ if (rc != CL5_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5Open: failed to initialize trimming\n");
+ goto done;
+ }
+
+ /* create the changelog directory if it does not exist */
+ rc = cl5CreateDirIfNeeded (dir);
+ if (rc != CL5_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5Open: failed to create changelog directory (%s)\n", dir);
+ goto done;
+ }
+
+ s_cl5Desc.dbDir = slapi_ch_strdup (dir);
+
+ /* check database version */
+ rc = _cl5CheckDBVersion ();
+ if (rc != CL5_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5Open: invalid db version\n");
+ goto done;
+ }
+
+ s_cl5Desc.dbOpenMode = openMode;
+
+ /* initialize db environment */
+ rc = _cl5AppInit (&didRecovery);
+ if (rc != CL5_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5Open: failed to initialize db environment\n");
+ goto done;
+ }
+
+ /* open database files */
+ rc = _cl5DBOpen (!didRecovery);
+ if (rc != CL5_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5Open: failed to open changelog database\n");
+
+ goto done;
+ }
+
+done:;
+
+ if (rc != CL5_SUCCESS)
+ {
+ _cl5Close ();
+ }
+
+ return rc;
+}
+
+int cl5CreateDirIfNeeded (const char *dirName)
+{
+ int rc;
+ char buff [MAXPATHLEN + 1];
+ char *t;
+
+ PR_ASSERT (dirName);
+
+ rc = PR_Access(dirName, PR_ACCESS_EXISTS);
+ if (rc == PR_SUCCESS)
+ {
+ return CL5_SUCCESS;
+ }
+
+ /* directory does not exist - try to create */
+ strncpy (buff, dirName, MAXPATHLEN);
+ t = strchr (buff, '/');
+
+ /* skip first slash */
+ if (t)
+ {
+ t = strchr (t+1, '/');
+ }
+
+ while (t)
+ {
+ *t = '\0';
+ if (PR_Access (buff, PR_ACCESS_EXISTS) != PR_SUCCESS)
+ {
+ rc = PR_MkDir (buff, DIR_CREATE_MODE);
+ if (rc != PR_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "cl5CreateDirIfNeeded: failed to create dir (%s); NSPR error - %d\n",
+ dirName, PR_GetError ());
+ return CL5_SYSTEM_ERROR;
+ }
+ }
+
+ *t++ = FILE_PATHSEP;
+
+ t = strchr (t, '/');
+ }
+
+ /* last piece */
+ rc = PR_MkDir (buff, DIR_CREATE_MODE);
+ if (rc != PR_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "cl5CreateDirIfNeeded: failed to create dir; NSPR error - %d\n",
+ PR_GetError ());
+ return CL5_SYSTEM_ERROR;
+ }
+
+ return CL5_SUCCESS;
+}
+
+static int _cl5RemoveEnv ()
+{
+ DB_ENV *dbEnv = NULL;
+ int rc = 0;
+
+ if ((rc = db_env_create(&dbEnv, 0)) != 0)
+ dbEnv = NULL;
+
+ if (dbEnv == NULL)
+ {
+ char *errstr = db_strerror(rc);
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5RemoveEnv: failed to allocate db environment; "
+ "db error - %d %s\n", rc, errstr ? errstr : "unknown");
+ return CL5_MEMORY_ERROR;
+ }
+ rc = dbEnv->remove(dbEnv, s_cl5Desc.dbDir, DB_FORCE);
+ if (0 != rc)
+ {
+ char *errstr = db_strerror(rc);
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5AppInit: failed to remove db environment; "
+ "db error - %d %s\n", rc, errstr ? errstr : "unknown");
+ return CL5_DB_ERROR;
+ }
+ return CL5_SUCCESS;
+}
+
+static int _cl5AppInit (PRBool *didRecovery)
+{
+ int rc;
+ unsigned int flags = DB_CREATE | DB_INIT_MPOOL | DB_THREAD;
+ DB_ENV *dbEnv;
+ if ((rc = db_env_create(&dbEnv, 0)) != 0)
+ dbEnv = NULL;
+
+ if (dbEnv == NULL)
+ {
+ char *errstr = db_strerror(rc);
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5AppInit: failed to allocate db environment; db error - %d (%s)\n",
+ rc, errstr ? errstr : "unknown");
+ return CL5_MEMORY_ERROR;
+ }
+
+ _cl5InitDBEnv (dbEnv);
+
+ if (didRecovery)
+ *didRecovery = PR_FALSE;
+
+ /* decide how two open based on the mode in which db is open */
+ switch (s_cl5Desc.dbOpenMode)
+ {
+ case CL5_OPEN_NORMAL:
+ flags |= DB_INIT_LOCK | DB_INIT_TXN | DB_INIT_LOG;
+ /* check if need to initiate recovery */
+ rc = _cl5CheckGuardian ();
+ if (rc != CL5_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5AppInit: recovering changelog after disorderly shutdown\n");
+ flags |= DB_RECOVER;
+ }
+ break;
+
+ case CL5_OPEN_RESTORE:
+ flags |= DB_INIT_LOCK | DB_INIT_TXN | DB_INIT_LOG;
+ break;
+
+ case CL5_OPEN_CLEAN_RECOVER:
+ flags |= DB_INIT_LOCK | DB_INIT_TXN | DB_INIT_LOG | DB_RECOVER;
+ break;
+
+ case CL5_OPEN_RESTORE_RECOVER:
+ flags |= DB_INIT_LOCK | DB_INIT_TXN | DB_INIT_LOG | DB_RECOVER_FATAL;
+ break;
+
+ case CL5_OPEN_LDIF2CL:
+ /* ONREPL - don't think we need any extra flags here */
+ break;
+ default:
+ /* fixme? CL5_OPEN_NONE */
+ break;
+ }
+
+ if (!s_cl5Desc.dbConfig.durableTrans)
+ {
+#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR >= 3200
+ dbEnv->set_flags(dbEnv, DB_TXN_NOSYNC, 1);
+#else
+ flags |= DB_TXN_NOSYNC;
+#endif
+ }
+
+ dbEnv->set_errcall(dbEnv, dblayer_log_print);
+
+ /* do recovery if necessary */
+ if ((flags & DB_RECOVER) || (flags & DB_RECOVER_FATAL))
+ {
+ if (CL5_OPEN_CLEAN_RECOVER == s_cl5Desc.dbOpenMode)
+ _cl5RemoveEnv();
+
+ rc = _cl5Recover (flags, dbEnv);
+ if (rc != CL5_SUCCESS)
+ {
+ char *errstr = db_strerror(rc);
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5AppInit: failed to recover changelog; db error - %d %s\n",
+ rc, errstr ? errstr : "unknown");
+
+ slapi_ch_free ((void **)&dbEnv);
+
+ return rc;
+ }
+
+ if (didRecovery)
+ *didRecovery = PR_TRUE;
+ flags &= ~(DB_RECOVER | DB_RECOVER_FATAL);
+ /* Need to reset the env */
+ /* Does this leak the dbEnv? */
+ if ((rc = db_env_create(&dbEnv, 0)) != 0)
+ dbEnv = NULL;
+
+ if (dbEnv == NULL)
+ {
+ char *errstr = db_strerror(rc);
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5AppInit: failed to allocate db environment after recovery; "
+ "db error - %d %s\n", rc, errstr ? errstr : "unknown");
+ return CL5_MEMORY_ERROR;
+ }
+ _cl5InitDBEnv (dbEnv);
+ }
+
+ rc = dbEnv->open(dbEnv, s_cl5Desc.dbDir, flags,
+ s_cl5Desc.dbConfig.fileMode);
+ if (rc == 0)
+ {
+ s_cl5Desc.dbEnv = dbEnv;
+ s_cl5Desc.dbEnvOpenFlags = flags;
+ return CL5_SUCCESS;
+ }
+ else
+ {
+ char *errstr = db_strerror(rc);
+ char flagstr[20];
+
+ flagstr[0] = 0;
+ /* EINVAL return means bad flags - let's see what the flags are */
+ if (rc == EINVAL)
+ {
+ sprintf(flagstr, "%u", flags);
+ }
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5AppInit: db environment open failed; db error - %d %s %s\n",
+ rc, errstr ? errstr : "unknown", flagstr);
+ slapi_ch_free ((void **)&dbEnv);
+ return CL5_DB_ERROR;
+ }
+}
+
+static int _cl5DBOpen ()
+{
+ PRBool dbFile;
+ PRDir *dir;
+ PRDirEntry *entry = NULL;
+ int rc;
+ Object *replica;
+
+ /* create lock that guarantees that each file is only added once to the list */
+ s_cl5Desc.fileLock = PR_NewLock ();
+
+ /* loop over all db files and open them; file name format is cl5_<dbid>.<dbext> */
+ dir = PR_OpenDir(s_cl5Desc.dbDir);
+ if (dir == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5DBOpen: failed to open changelog dir; NSPR error - %d\n",
+ PR_GetError ());
+ return CL5_SYSTEM_ERROR;
+
+ }
+
+ /* initialize set of db file objects */
+ s_cl5Desc.dbFiles = objset_new(NULL);
+ while (NULL != (entry = PR_ReadDir(dir, PR_SKIP_DOT | PR_SKIP_DOT_DOT)))
+ {
+ if (NULL == entry->name)
+ {
+ break;
+ }
+
+ dbFile = _cl5FileName2Replica (entry->name, &replica);
+ if (dbFile) /* this is db file, not a log or dbversion; those are just skipped */
+ {
+ /* we only open files for existing replicas */
+ if (replica)
+ {
+ rc = _cl5DBOpenFile (replica, NULL /* file object */,
+ PR_FALSE /* check for duplicates */);
+ if (rc != CL5_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5DBOpen: "
+ "Error opening file %s\n",
+ entry->name);
+ return rc;
+ }
+
+ object_release (replica);
+ }
+ else /* there is no matching replica for the file - remove */
+ {
+ char fullpathname[MAXPATHLEN];
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5DBOpen: "
+ "file %s has no matching replica; removing\n", entry->name);
+
+ PR_snprintf(fullpathname, MAXPATHLEN, "%s/%s", s_cl5Desc.dbDir, entry->name);
+ if (PR_Delete(fullpathname) != PR_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5DBOpen: "
+ "failed to remove (%s) file; NSPR error - %d\n",
+ entry->name, PR_GetError ());
+
+ }
+ }
+ }
+ }
+
+ PR_CloseDir(dir);
+
+ return CL5_SUCCESS;
+}
+
+/* this function assumes that the entry was validated
+ using IsValidOperation
+
+ Data in db format:
+ ------------------
+ <1 byte version><1 byte change_type><sizeof time_t time><null terminated csn>
+ <null terminated uniqueid><null terminated targetdn>
+ [<null terminated newrdn><1 byte deleteoldrdn>][<4 byte mod count><mod1><mod2>....]
+
+ mod format:
+ -----------
+ <1 byte modop><null terminated attr name><4 byte value count>
+ <4 byte value size><value1><4 byte value size><value2>
+*/
+static int _cl5Entry2DBData (const CL5Entry *entry, char **data, PRUint32 *len)
+{
+ int size = 1 /* version */ + 1 /* operation type */ + sizeof (time_t);
+ char *pos;
+ PRUint32 t;
+ slapi_operation_parameters *op;
+ LDAPMod **add_mods = NULL;
+ char *rawDN = NULL;
+ char s[CSN_STRSIZE];
+
+ PR_ASSERT (entry && entry->op && data && len);
+
+ op = entry->op;
+
+ /* compute size of the buffer needed to hold the data */
+ size += CSN_STRSIZE;
+ size += strlen (op->target_address.uniqueid) + 1;
+
+ switch (op->operation_type)
+ {
+ case SLAPI_OPERATION_ADD: if (op->p.p_add.parentuniqueid)
+ size += strlen (op->p.p_add.parentuniqueid) + 1;
+ else
+ size ++; /* we just store NULL char */
+ slapi_entry2mods (op->p.p_add.target_entry, &rawDN/* dn */, &add_mods);
+ size += strlen (rawDN) + 1;
+ size += _cl5GetModsSize (add_mods);
+ break;
+
+ case SLAPI_OPERATION_MODIFY: size += strlen (op->target_address.dn) + 1;
+ size += _cl5GetModsSize (op->p.p_modify.modify_mods);
+ break;
+
+ case SLAPI_OPERATION_MODRDN: size += strlen (op->target_address.dn) + 1;
+ /* 1 for deleteoldrdn */
+ size += strlen (op->p.p_modrdn.modrdn_newrdn) + 2;
+ if (op->p.p_modrdn.modrdn_newsuperior_address.dn)
+ size += strlen (op->p.p_modrdn.modrdn_newsuperior_address.dn) + 1;
+ else
+ size ++; /* for NULL char */
+ if (op->p.p_modrdn.modrdn_newsuperior_address.uniqueid)
+ size += strlen (op->p.p_modrdn.modrdn_newsuperior_address.uniqueid) + 1;
+ else
+ size ++; /* for NULL char */
+ size += _cl5GetModsSize (op->p.p_modrdn.modrdn_mods);
+ break;
+
+ case SLAPI_OPERATION_DELETE: size += strlen (op->target_address.dn) + 1;
+ break;
+ }
+
+ /* allocate data buffer */
+ (*data) = (char *) slapi_ch_malloc (size);
+ if ((*data) == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5Entry2DBData: failed to allocate data buffer\n");
+ return CL5_MEMORY_ERROR;
+ }
+
+ /* fill in the data buffer */
+ pos = *data;
+ /* write a byte of version */
+ (*pos) = V_5;
+ pos ++;
+ /* write change type */
+ (*pos) = (unsigned char)op->operation_type;
+ pos ++;
+ /* write time */
+ t = PR_htonl((PRUint32)entry->time);
+ memcpy (pos, &t, sizeof (t));
+ pos += sizeof (t);
+ /* write csn */
+ _cl5WriteString (csn_as_string(op->csn,PR_FALSE,s), &pos);
+ /* write UniqueID */
+ _cl5WriteString (op->target_address.uniqueid, &pos);
+
+ /* figure out what else we need to write depending on the operation type */
+ switch (op->operation_type)
+ {
+ case SLAPI_OPERATION_ADD: _cl5WriteString (op->p.p_add.parentuniqueid, &pos);
+ _cl5WriteString (rawDN, &pos);
+ _cl5WriteMods (add_mods, &pos);
+ slapi_ch_free ((void**)&rawDN);
+ ldap_mods_free (add_mods, 1);
+ break;
+
+ case SLAPI_OPERATION_MODIFY: _cl5WriteString (op->target_address.dn, &pos);
+ _cl5WriteMods (op->p.p_modify.modify_mods, &pos);
+ break;
+
+ case SLAPI_OPERATION_MODRDN: _cl5WriteString (op->target_address.dn, &pos);
+ _cl5WriteString (op->p.p_modrdn.modrdn_newrdn, &pos);
+ *pos = (PRUint8)op->p.p_modrdn.modrdn_deloldrdn;
+ pos ++;
+ _cl5WriteString (op->p.p_modrdn.modrdn_newsuperior_address.dn, &pos);
+ _cl5WriteString (op->p.p_modrdn.modrdn_newsuperior_address.uniqueid, &pos);
+ _cl5WriteMods (op->p.p_modrdn.modrdn_mods, &pos);
+ break;
+
+ case SLAPI_OPERATION_DELETE: _cl5WriteString (op->target_address.dn, &pos);
+ break;
+ }
+
+ (*len) = size;
+
+ return CL5_SUCCESS;
+}
+
+/*
+ Data in db format:
+ ------------------
+ <1 byte version><1 byte change_type><sizeof time_t time><null terminated dbid>
+ <null terminated csn><null terminated uniqueid><null terminated targetdn>
+ [<null terminated newrdn><1 byte deleteoldrdn>][<4 byte mod count><mod1><mod2>....]
+
+ mod format:
+ -----------
+ <1 byte modop><null terminated attr name><4 byte value count>
+ <4 byte value size><value1><4 byte value size><value2>
+*/
+
+
+int
+cl5DBData2Entry (const char *data, PRUint32 len, CL5Entry *entry)
+{
+ int rc;
+ PRUint8 version;
+ char *pos = (char *)data;
+ char *strCSN;
+ PRUint32 thetime;
+ slapi_operation_parameters *op;
+ LDAPMod **add_mods;
+ char *rawDN;
+ char s[CSN_STRSIZE];
+
+ PR_ASSERT (data && entry && entry->op);
+
+ /* ONREPL - check that we do not go beyond the end of the buffer */
+
+ /* read byte of version */
+ version = (PRUint8)(*pos);
+ if (version != V_5)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5DBData2Entry: invalid data version\n");
+ return CL5_BAD_FORMAT;
+ }
+
+ op = entry->op;
+
+ pos += sizeof(version);
+
+ /* read change type */
+ op->operation_type = (PRUint8)(*pos);
+ pos ++;
+
+ /* need to do the copy first, to skirt around alignment problems on
+ certain architectures */
+ memcpy((char *)&thetime,pos,sizeof(thetime));
+ entry->time = (time_t)PR_ntohl(thetime);
+ pos += sizeof (thetime);
+
+ /* read csn */
+ _cl5ReadString (&strCSN, &pos);
+ if (op->csn == NULL || strcmp (strCSN, csn_as_string(op->csn,PR_FALSE,s)) != 0)
+ {
+ op->csn = csn_new_by_string (strCSN);
+ }
+ slapi_ch_free ((void**)&strCSN);
+
+ /* read UniqueID */
+ _cl5ReadString (&op->target_address.uniqueid, &pos);
+
+ /* figure out what else we need to read depending on the operation type */
+ switch (op->operation_type)
+ {
+ case SLAPI_OPERATION_ADD: _cl5ReadString (&op->p.p_add.parentuniqueid, &pos);
+ /* richm: need to free parentuniqueid */
+ _cl5ReadString (&rawDN, &pos);
+ op->target_address.dn = rawDN;
+ /* convert mods to entry */
+ rc = _cl5ReadMods (&add_mods, &pos);
+ slapi_mods2entry (&(op->p.p_add.target_entry), rawDN, add_mods);
+ ldap_mods_free (add_mods, 1);
+ break;
+
+ case SLAPI_OPERATION_MODIFY: _cl5ReadString (&op->target_address.dn, &pos);
+ rc = _cl5ReadMods (&op->p.p_modify.modify_mods, &pos);
+ break;
+
+ case SLAPI_OPERATION_MODRDN: _cl5ReadString (&op->target_address.dn, &pos);
+ _cl5ReadString (&op->p.p_modrdn.modrdn_newrdn, &pos);
+ op->p.p_modrdn.modrdn_deloldrdn = *pos;
+ pos ++;
+ _cl5ReadString (&op->p.p_modrdn.modrdn_newsuperior_address.dn, &pos);
+ _cl5ReadString (&op->p.p_modrdn.modrdn_newsuperior_address.uniqueid, &pos);
+ rc = _cl5ReadMods (&op->p.p_modrdn.modrdn_mods, &pos);
+ break;
+
+ case SLAPI_OPERATION_DELETE: _cl5ReadString (&op->target_address.dn, &pos);
+ rc = CL5_SUCCESS;
+ break;
+
+ default: rc = CL5_BAD_FORMAT;
+ slapi_log_error(SLAPI_LOG_FATAL,
+ repl_plugin_name_cl,
+ "cl5DBData2Entry: failed to format entry\n");
+ break;
+ }
+
+ return rc;
+}
+
+/* thread management functions */
+static int _cl5DispatchDBThreads ()
+{
+ if (NULL == PR_CreateThread (PR_USER_THREAD, (VFP)(void *)_cl5DeadlockMain,
+ NULL, PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
+ PR_UNJOINABLE_THREAD, DEFAULT_THREAD_STACKSIZE))
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5DispatchDBThreads: failed to create deadlock thread; "
+ "NSPR error - %d\n", PR_GetError ());
+ return CL5_SYSTEM_ERROR;
+ }
+
+ if (NULL == PR_CreateThread (PR_USER_THREAD, (VFP)(void *)_cl5CheckpointMain,
+ NULL, PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
+ PR_UNJOINABLE_THREAD, DEFAULT_THREAD_STACKSIZE))
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5DispatchDBThreads: failed to create checkpoint thread; "
+ "NSPR error - %d\n", PR_GetError ());
+ return CL5_SYSTEM_ERROR;
+ }
+
+ if (NULL == PR_CreateThread (PR_USER_THREAD, (VFP)(void *)_cl5TrickleMain,
+ NULL, PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
+ PR_UNJOINABLE_THREAD, DEFAULT_THREAD_STACKSIZE) )
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5DispatchDBThreads: failed to create trickle thread; "
+ "NSPR error - %d\n", PR_GetError ());
+ return CL5_SYSTEM_ERROR;
+ }
+
+ if (NULL == PR_CreateThread (PR_USER_THREAD, (VFP)(void*)_cl5TrimMain,
+ NULL, PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
+ PR_UNJOINABLE_THREAD, DEFAULT_THREAD_STACKSIZE) )
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5DispatchDBThreads: failed to create trimming thread; "
+ "NSPR error - %d\n", PR_GetError ());
+ return CL5_SYSTEM_ERROR;
+ }
+
+ return CL5_SUCCESS;
+}
+
+static int _cl5AddThread ()
+{
+ /* lock the state lock so that nobody can change the state
+ while backup is in progress
+ */
+ PR_RWLock_Rlock (s_cl5Desc.stLock);
+
+ /* open changelog if it is not already open */
+ if (s_cl5Desc.dbState != CL5_STATE_OPEN)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5AddThread: invalid changelog state - %d\n", s_cl5Desc.dbState);
+ PR_RWLock_Unlock (s_cl5Desc.stLock);
+ return CL5_BAD_STATE;
+ }
+
+ /* increment global thread count to make sure that changelog does not close while
+ backup is in progress */
+ PR_AtomicIncrement (&s_cl5Desc.threadCount);
+
+ PR_RWLock_Unlock (s_cl5Desc.stLock);
+
+ return CL5_SUCCESS;
+}
+
+static void _cl5RemoveThread ()
+{
+ PR_ASSERT (s_cl5Desc.threadCount > 0);
+ PR_AtomicDecrement (&s_cl5Desc.threadCount);
+}
+
+/* data conversion functions */
+static void _cl5WriteString (const char *str, char **buff)
+{
+ if (str)
+ {
+ strcpy (*buff, str);
+ (*buff) += strlen (str) + 1;
+ }
+ else /* just write NULL char */
+ {
+ (**buff) = '\0';
+ (*buff) ++;
+ }
+}
+
+static void _cl5ReadString (char **str, char **buff)
+{
+ if (str)
+ {
+ int len = strlen (*buff);
+
+ if (len)
+ {
+ *str = slapi_ch_strdup (*buff);
+ (*buff) += len + 1;
+ }
+ else /* just null char - skip it */
+ {
+ *str = NULL;
+ (*buff) ++;
+ }
+ }
+ else /* just skip this string */
+ {
+ (*buff) += strlen (*buff) + 1;
+ }
+}
+
+/* mods format:
+ -----------
+ <4 byte mods count><mod1><mod2>...
+
+ mod format:
+ -----------
+ <1 byte modop><null terminated attr name><4 byte count>
+ <4 byte size><value1><4 byte size><value2>...
+ */
+static void _cl5WriteMods (LDAPMod **mods, char **buff)
+{
+ PRInt32 i;
+ char *mod_start;
+ PRInt32 count;
+
+ if (mods == NULL)
+ return;
+
+ /* skip mods count */
+ mod_start = (*buff) + sizeof (count);
+
+ /* write mods*/
+ for (i=0; mods[i]; i++)
+ {
+ _cl5WriteMod (mods[i], &mod_start);
+ }
+
+ count = PR_htonl(i);
+ memcpy (*buff, &count, sizeof (count));
+
+ (*buff) = mod_start;
+}
+
+static void _cl5WriteMod (LDAPMod *mod, char **buff)
+{
+ char *pos;
+ PRInt32 count;
+ struct berval *bv;
+ Slapi_Mod smod;
+
+ slapi_mod_init_byref(&smod, mod);
+
+ pos = *buff;
+ /* write mod op */
+ *pos = (PRUint8)slapi_mod_get_operation (&smod);
+ pos ++;
+ /* write attribute name */
+ _cl5WriteString (slapi_mod_get_type (&smod), &pos);
+
+ /* write value count */
+ count = PR_htonl(slapi_mod_get_num_values(&smod));
+ memcpy (pos, &count, sizeof (count));
+ pos += sizeof (PRInt32);
+
+ bv = slapi_mod_get_first_value (&smod);
+ while (bv)
+ {
+ _cl5WriteBerval (bv, &pos);
+ bv = slapi_mod_get_next_value (&smod);
+ }
+
+ (*buff) = pos;
+
+ slapi_mod_done (&smod);
+}
+
+/* mods format:
+ -----------
+ <4 byte mods count><mod1><mod2>...
+
+ mod format:
+ -----------
+ <1 byte modop><null terminated attr name><4 byte count>
+ {<4 byte size><value1><4 byte size><value2>... ||
+ <null terminated str1> <null terminated str2>...}
+ */
+
+static int _cl5ReadMods (LDAPMod ***mods, char **buff)
+{
+ char *pos = *buff;
+ int i;
+ int rc;
+ PRInt32 mod_count;
+ Slapi_Mods smods;
+ Slapi_Mod smod;
+
+ /* need to copy first, to skirt around alignment problems on certain
+ architectures */
+ memcpy((char *)&mod_count,*buff,sizeof(mod_count));
+ mod_count = PR_ntohl(mod_count);
+ pos += sizeof (mod_count);
+
+ slapi_mods_init (&smods , mod_count);
+
+ for (i = 0; i < mod_count; i++)
+ {
+ rc = _cl5ReadMod (&smod, &pos);
+ if (rc != CL5_SUCCESS)
+ {
+ slapi_mods_done(&smods);
+ return rc;
+ }
+
+ slapi_mods_add_smod(&smods, &smod);
+ }
+
+ *buff = pos;
+
+ *mods = slapi_mods_get_ldapmods_passout (&smods);
+ slapi_mods_done(&smods);
+
+ return CL5_SUCCESS;
+}
+
+static int _cl5ReadMod (Slapi_Mod *smod, char **buff)
+{
+ char *pos = *buff;
+ int i;
+ PRInt32 val_count;
+ char *type;
+ int op;
+ struct berval bv;
+
+ op = (*pos) & 0x000000FF;
+ pos ++;
+ _cl5ReadString (&type, &pos);
+
+ /* need to do the copy first, to skirt around alignment problems on
+ certain architectures */
+ memcpy((char *)&val_count,pos,sizeof(val_count));
+ val_count = PR_ntohl(val_count);
+ pos += sizeof (PRInt32);
+
+ slapi_mod_init(smod, val_count);
+ slapi_mod_set_operation (smod, op|LDAP_MOD_BVALUES);
+ slapi_mod_set_type (smod, type);
+ slapi_ch_free ((void**)&type);
+
+ for (i = 0; i < val_count; i++)
+ {
+ _cl5ReadBerval (&bv, &pos);
+ slapi_mod_add_value (smod, &bv);
+ slapi_ch_free((void **) &bv.bv_val);
+ }
+
+ (*buff) = pos;
+
+ return CL5_SUCCESS;
+}
+
+static int _cl5GetModsSize (LDAPMod **mods)
+{
+ int size;
+ int i;
+
+ if (mods == NULL)
+ return 0;
+
+ size = sizeof (PRInt32);
+ for (i=0; mods[i]; i++)
+ {
+ size += _cl5GetModSize (mods[i]);
+ }
+
+ return size;
+}
+
+static int _cl5GetModSize (LDAPMod *mod)
+{
+ int size;
+ int i;
+
+ size = 1 + strlen (mod->mod_type) + 1 + sizeof (mod->mod_op);
+ i = 0;
+ if (mod->mod_op & LDAP_MOD_BVALUES) /* values are in binary form */
+ {
+ while (mod->mod_bvalues != NULL && mod->mod_bvalues[i] != NULL)
+ {
+ size += mod->mod_bvalues[i]->bv_len + sizeof (mod->mod_bvalues[i]->bv_len);
+ i++;
+ }
+ }
+ else /* string data */
+ {
+ PR_ASSERT(0); /* ggood string values should never be used in the server */
+ }
+
+ return size;
+}
+
+static void _cl5ReadBerval (struct berval *bv, char** buff)
+{
+ PRUint32 length = 0;
+ PRUint32 net_length = 0;
+
+ PR_ASSERT (bv && buff);
+
+ /***PINAKI need to do the copy first, to skirt around alignment problems on
+ certain architectures */
+ /* DBDB : struct berval.bv_len is defined as unsigned long
+ * But code here expects it to be 32-bits in size.
+ * On 64-bit machines, this is not the case.
+ * I changed the code to consistently use 32-bit (4-byte)
+ * values on the encoded side. This means that it's
+ * possible to generate a huge berval that will not
+ * be encoded properly. However, this seems unlikely
+ * to happen in reality, and I felt that retaining the
+ * old on-disk format for the changely in the 64-bit
+ * version of the server was important.
+ */
+
+ memcpy((char *)&net_length, *buff, sizeof(net_length));
+ length = PR_ntohl(net_length);
+ *buff += sizeof(net_length);
+ bv->bv_len = length;
+
+ if (bv->bv_len > 0) {
+ bv->bv_val = (char*)slapi_ch_malloc (bv->bv_len);
+ memcpy (bv->bv_val, *buff, bv->bv_len);
+ *buff += bv->bv_len;
+ }
+ else {
+ bv->bv_val = NULL;
+ }
+}
+
+static void _cl5WriteBerval (struct berval *bv, char** buff)
+{
+ PRUint32 length = 0;
+ PRUint32 net_length = 0;
+
+ length = (PRUint32) bv->bv_len;
+ net_length = PR_htonl(length);
+
+ memcpy(*buff, &net_length, sizeof (net_length));
+ *buff += sizeof (net_length);
+ memcpy (*buff, bv->bv_val, length);
+ *buff += length;
+}
+
+/* data format: <value count> <value size> <value> <value size> <value> ..... */
+static int _cl5ReadBervals (struct berval ***bv, char** buff, unsigned int size)
+{
+ PRInt32 count;
+ int i;
+ char *pos;
+
+ PR_ASSERT (bv && buff);
+
+ /* ONREPL - need to check that we don't go beyond the end of the buffer */
+
+ pos = *buff;
+ memcpy((char *)&count, pos, sizeof(count));
+ count = PR_htonl (count);
+ pos += sizeof(count);
+
+ /* allocate bervals */
+ *bv = (struct berval **)slapi_ch_malloc ((count + 1) * sizeof (struct berval*));
+ if (*bv == NULL)
+ {
+ return CL5_MEMORY_ERROR;
+ }
+
+ for (i = 0; i < count; i++)
+ {
+ (*bv)[i] = (struct berval *)slapi_ch_malloc (sizeof (struct berval));
+ if ((*bv)[i] == NULL)
+ {
+ ber_bvecfree(*bv);
+ return CL5_MEMORY_ERROR;
+ }
+
+ _cl5ReadBerval ((*bv)[i], &pos);
+ }
+
+ (*bv)[count] = NULL;
+ *buff = pos;
+
+ return CL5_SUCCESS;
+}
+
+/* data format: <value count> <value size> <value> <value size> <value> ..... */
+static int _cl5WriteBervals (struct berval **bv, char** buff, unsigned int *size)
+{
+ PRInt32 count, net_count;
+ char *pos;
+ int i;
+
+ PR_ASSERT (bv && buff && size);
+
+ /* compute number of values and size of the buffer to hold them */
+ *size = sizeof (count);
+ for (count = 0; bv[count]; count ++)
+ {
+ *size += sizeof (bv[count]->bv_len) + bv[count]->bv_len;
+ }
+
+ /* allocate buffer */
+ *buff = (char*) slapi_ch_malloc (*size);
+ if (*buff == NULL)
+ {
+ *size = 0;
+ return CL5_MEMORY_ERROR;
+ }
+
+ /* fill the buffer */
+ pos = *buff;
+ net_count = PR_htonl(count);
+ memcpy (pos, &net_count, sizeof (net_count));
+ pos += sizeof (net_count);
+ for (i = 0; i < count; i ++)
+ {
+ _cl5WriteBerval (bv[i], &pos);
+ }
+
+ return CL5_SUCCESS;
+}
+
+static int _cl5DeadlockMain (void *param)
+{
+ PRIntervalTime interval;
+ int rc;
+
+ PR_AtomicIncrement (&s_cl5Desc.threadCount);
+ interval = PR_MillisecondsToInterval(100);
+ while (s_cl5Desc.dbState != CL5_STATE_CLOSING)
+ {
+ int aborted;
+ if ((rc = LOCK_DETECT(s_cl5Desc.dbEnv, 0, DB_LOCK_YOUNGEST, &aborted)) != 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5DeadlockMain: lock_detect failed (%d transaction%s aborted); db error - %d %s\n",
+ aborted, (aborted == 1)? "":"s", rc, db_strerror(rc));
+ }
+ else if (aborted)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5DeadlockMain: lock_detect succeeded, but %d transaction%s ha%s been aborted\n",
+ aborted, (aborted == 1)? "":"s", (aborted == 1)? "s":"ve");
+ }
+
+ DS_Sleep(interval);
+ }
+
+ PR_AtomicDecrement (&s_cl5Desc.threadCount);
+ return 0;
+}
+
+static int _cl5CheckpointMain (void *param)
+{
+ time_t lastCheckpointCompletion = 0;
+ PRIntervalTime interval;
+ int rc = -1;
+
+ PR_AtomicIncrement (&s_cl5Desc.threadCount);
+
+ interval = PR_MillisecondsToInterval(1000);
+ lastCheckpointCompletion = current_time();
+
+ while (s_cl5Desc.dbState != CL5_STATE_CLOSING)
+ {
+ /* Check to see if the checkpoint interval has elapsed */
+ if (current_time() - lastCheckpointCompletion > s_cl5Desc.dbConfig.checkpointInterval)
+ {
+ rc = TXN_CHECKPOINT(s_cl5Desc.dbEnv, 0, 0, 0);
+ if (rc == 0)
+ {
+ lastCheckpointCompletion = current_time();
+ }
+#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100
+ else if (rc != DB_INCOMPLETE) /* real error happened */
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5CheckpointMain: checkpoint failed, db error - %d %s\n",
+ rc, db_strerror(rc));
+ }
+#endif
+
+ /* According to dboreham, we are doing checkpoint twice
+ to reduce the number of transaction log files which need
+ to be retained at any time. */
+ rc = TXN_CHECKPOINT(s_cl5Desc.dbEnv, 0, 0, 0);
+ if (rc == 0)
+ {
+ lastCheckpointCompletion = current_time();
+ }
+#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100
+ else if (rc != DB_INCOMPLETE) /* real error happened */
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5CheckpointMain: checkpoint failed, db error - %d %s\n",
+ rc, db_strerror(rc));
+ }
+#endif
+
+ /* check if we should truncate logs */
+ if (s_cl5Desc.dbConfig.circularLogging)
+ {
+ char **list = NULL;
+ char **listp = NULL;
+ int rc = -1;
+ char filename[MAXPATHLEN + 1];
+
+ /* find out which log files don't contain active txns */
+ rc = LOG_ARCHIVE(s_cl5Desc.dbEnv, &list, 0, malloc);
+ if (0 == rc && NULL != list)
+ {
+ /* zap 'em ! */
+ for (listp = list; *listp != NULL; ++listp)
+ {
+ PR_snprintf(filename, MAXPATHLEN, "%s/%s", s_cl5Desc.dbDir,*listp);
+ PR_Delete (filename);
+ }
+ slapi_ch_free((void **)&list);
+ }
+ }
+ }
+
+ /* sleep for a while */
+ /* why aren't we sleeping exactly the right amount of time ? */
+ /* answer---because the interval might be changed after the server starts up */
+ DS_Sleep(interval);
+ }
+
+ PR_AtomicDecrement (&s_cl5Desc.threadCount);
+ return 0;
+}
+
+static int _cl5TrickleMain (void *param)
+{
+ PRIntervalTime interval;
+ int pages_written;
+ int rc;
+
+ PR_AtomicIncrement (&s_cl5Desc.threadCount);
+ interval = PR_MillisecondsToInterval(1000);
+ while (s_cl5Desc.dbState != CL5_STATE_CLOSING)
+ {
+ if ((rc = MEMP_TRICKLE(s_cl5Desc.dbEnv,
+ s_cl5Desc.dbConfig.tricklePercentage, &pages_written)) != 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5TrickleMain: memp_trickle failed; db error - %d %s\n",
+ rc, db_strerror(rc));
+ }
+
+ DS_Sleep(interval);
+ }
+
+ PR_AtomicDecrement (&s_cl5Desc.threadCount);
+
+ return 0;
+}
+
+/* upgrade from db33 to db41
+ * 1. Run recovery on the database environment using the DB_ENV->open method
+ * 2. Remove any Berkeley DB environment using the DB_ENV->remove method
+ * 3. extention .db3 -> .db4 ### koko kara !!!
+ */
+static int _cl5Upgrade3_4(char *fromVersion, char *toVersion)
+{
+ PRDir *dir = NULL;
+ PRDirEntry *entry = NULL;
+ DB *thisdb = NULL;
+ CL5OpenMode backup;
+ int rc = 0;
+
+ backup = s_cl5Desc.dbOpenMode;
+ s_cl5Desc.dbOpenMode = CL5_OPEN_CLEAN_RECOVER;
+ /* CL5_OPEN_CLEAN_RECOVER does 1 and 2 */
+ rc = _cl5AppInit (NULL);
+ if (rc != CL5_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5Upgrade3_4: failed to open the db env\n");
+ return rc;
+ }
+
+ dir = PR_OpenDir(s_cl5Desc.dbDir);
+ if (dir == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5Upgrade3_4: failed to open changelog dir %s; NSPR error - %d\n",
+ s_cl5Desc.dbDir, PR_GetError ());
+ goto out;
+ }
+
+ while (NULL != (entry = PR_ReadDir(dir, PR_SKIP_DOT | PR_SKIP_DOT_DOT)))
+ {
+ if (NULL == entry->name)
+ {
+ break;
+ }
+ if (_cl5FileEndsWith(entry->name, DB_EXTENSION_DB3))
+ {
+ char oName [MAXPATHLEN + 1];
+ char nName [MAXPATHLEN + 1];
+ char *p = NULL;
+ char c;
+ int baselen = 0;
+ PR_snprintf(oName, MAXPATHLEN, "%s/%s", s_cl5Desc.dbDir, entry->name);
+ p = strstr(oName, DB_EXTENSION_DB3);
+ if (NULL == p)
+ {
+ continue;
+ }
+ /* db->rename closes DB; need to create every time */
+ rc = db_create(&thisdb, s_cl5Desc.dbEnv, 0);
+ if (0 != rc) {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5Upgrade3_4: failed to get db handle\n");
+ goto out;
+ }
+
+ baselen = p - oName;
+ c = *p;
+ *p = '\0';
+ PR_snprintf(nName, MAXPATHLEN+1, "%s", oName);
+ PR_snprintf(nName + baselen, MAXPATHLEN+1-baselen, "%s", DB_EXTENSION);
+ *p = c;
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5Upgrade3_4: renaming %s to %s\n", oName, nName);
+ rc = thisdb->rename(thisdb, (const char *)oName, NULL /* subdb */,
+ (const char *)nName, 0);
+ if (rc != PR_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5Upgrade3_4: failed to rename file (%s -> %s); "
+ "db error - %d %s\n", oName, nName, rc, db_strerror(rc));
+ break;
+ }
+ }
+ }
+ /* update the version file */
+ _cl5WriteDBVersion ();
+
+ /* update the guardian file */
+ _cl5WriteGuardian ();
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "Upgrading from %s to %s is successfully done (%s)\n",
+ fromVersion, toVersion, s_cl5Desc.dbDir);
+out:
+ if (NULL != dir)
+ {
+ PR_CloseDir(dir);
+ }
+ if (s_cl5Desc.dbEnv)
+ {
+ DB_ENV *dbEnv = s_cl5Desc.dbEnv;
+ dbEnv->close(dbEnv, 0);
+ s_cl5Desc.dbEnv = NULL;
+ }
+ return rc;
+}
+
+static int _cl5CheckDBVersion ()
+{
+ char clVersion [VERSION_SIZE + 1];
+ char dbVersion [VERSION_SIZE + 1];
+ int rc;
+
+ if (!cl5Exist (s_cl5Desc.dbDir))
+ {
+ /* this is new changelog - write DB version and guardian file */
+ rc = _cl5WriteDBVersion ();
+ if (rc == CL5_SUCCESS) {
+ rc = _cl5WriteGuardian();
+ }
+ }
+ else
+ {
+ PR_snprintf (clVersion, VERSION_SIZE, "%s/%s/%s", CL5_TYPE, REPL_PLUGIN_NAME,
+ CHANGELOG_DB_VERSION);
+ rc = _cl5ReadDBVersion (s_cl5Desc.dbDir, dbVersion);
+
+ if (rc != CL5_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5CheckDBVersion: invalid dbversion\n");
+ rc = CL5_BAD_DBVERSION;
+ }
+ else if (strcasecmp (clVersion, dbVersion) != 0)
+ {
+ char prevClVersion [VERSION_SIZE + 1];
+ PR_snprintf (prevClVersion, VERSION_SIZE, "%s/%s/%s",
+ CL5_TYPE, REPL_PLUGIN_NAME, CHANGELOG_DB_VERSION_PREV);
+ if (strcasecmp (prevClVersion, dbVersion) == 0)
+ {
+ /* upgrade */
+ rc = _cl5Upgrade3_4(prevClVersion, clVersion);
+ if (rc != CL5_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5CheckDBVersion: upgrade %s -> %s failed\n",
+ CHANGELOG_DB_VERSION_PREV, CHANGELOG_DB_VERSION);
+ rc = CL5_BAD_DBVERSION;
+ }
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5CheckDBVersion: invalid dbversion\n");
+ rc = CL5_BAD_DBVERSION;
+ }
+ }
+
+ }
+
+ return rc;
+}
+
+static int _cl5ReadDBVersion (const char *dir, char *clVersion)
+{
+ int rc;
+ PRFileDesc *file;
+ char fName [MAXPATHLEN + 1];
+ char buff [BUFSIZ];
+ PRInt32 size;
+ char *tok;
+ char * iter = NULL;
+
+ if (clVersion)
+ {
+ clVersion [0] = '\0';
+ }
+
+ PR_snprintf (fName, MAXPATHLEN, "%s/%s", dir, VERSION_FILE);
+
+ file = PR_Open (fName, PR_RDONLY, 777);
+ if (file == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5ReadDBVersion: failed to open DBVERSION; NSPR error - %d\n",
+ PR_GetError ());
+ return CL5_SYSTEM_ERROR;
+ }
+
+ size = slapi_read_buffer (file, buff, BUFSIZ);
+ if (size < 0)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5ReadDBVersion: failed to read DBVERSION; NSPR error - %d\n",
+ PR_GetError ());
+ PR_Close (file);
+ return CL5_SYSTEM_ERROR;
+ }
+
+ /* parse the data */
+ buff[size]= '\0';
+ tok = ldap_utf8strtok_r (buff, "\n", &iter);
+ if (tok)
+ {
+ if (clVersion)
+ {
+ strcpy(clVersion, tok);
+ }
+ }
+
+ rc = PR_Close (file);
+ if (rc != PR_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5ReadDBVersion: failed to close DBVERSION; NSPR error - %d\n",
+ PR_GetError ());
+ return CL5_SYSTEM_ERROR;
+ }
+
+ return CL5_SUCCESS;
+}
+
+static int _cl5WriteDBVersion ()
+{
+ int rc;
+ PRFileDesc *file;
+ char fName [MAXPATHLEN + 1];
+ char clVersion [VERSION_SIZE + 1];
+ PRInt32 len, size;
+
+ PR_snprintf (fName, MAXPATHLEN, "%s/%s", s_cl5Desc.dbDir, VERSION_FILE);
+
+ file = PR_Open (fName, PR_WRONLY | PR_CREATE_FILE, s_cl5Desc.dbConfig.fileMode);
+ if (file == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5WriteDBVersion: failed to open DBVERSION; NSPR error - %d\n",
+ PR_GetError ());
+ return CL5_SYSTEM_ERROR;
+ }
+
+ /* write changelog version */
+ PR_snprintf (clVersion, VERSION_SIZE, "%s/%s/%s\n", CL5_TYPE, REPL_PLUGIN_NAME,
+ CHANGELOG_DB_VERSION);
+
+ len = strlen (clVersion);
+ size = slapi_write_buffer (file, clVersion, len);
+ if (size != len)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5WriteDBVersion: failed to write DBVERSION; NSPR error - %d\n",
+ PR_GetError ());
+ PR_Close (file);
+ return CL5_SYSTEM_ERROR;
+ }
+
+ rc = PR_Close (file);
+ if (rc != PR_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5WriteDBVersion: failed to close DBVERSION; NSPR error - %d\n",
+ PR_GetError ());
+ return CL5_SYSTEM_ERROR;
+ }
+
+ return CL5_SUCCESS;
+}
+
+/* for now guardian file is just like dbversion file */
+static int _cl5CheckGuardian ()
+{
+ char plVersion [VERSION_SIZE + 1];
+ char dbVersion [VERSION_SIZE + 1];
+ int rc;
+
+ /* new changelog - no guardian file */
+ if (!cl5Exist(s_cl5Desc.dbDir))
+ {
+ return CL5_SUCCESS;
+ }
+ else
+ {
+ PR_snprintf (plVersion, VERSION_SIZE, "%s/%s/%s", CL5_TYPE, REPL_PLUGIN_NAME,
+ CHANGELOG_DB_VERSION);
+ rc = _cl5ReadGuardian (dbVersion);
+
+ if (rc != CL5_SUCCESS || strcasecmp (plVersion, dbVersion) != 0)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5CheckGuardian: missing or invalid guardian file\n");
+ return (CL5_BAD_FORMAT);
+ }
+
+ /* remove guardian file */
+ rc = _cl5RemoveGuardian ();
+ if (rc != CL5_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5CheckGuardian: failed to remove guardian file\n");
+ }
+ }
+
+ return rc;
+}
+
+static int _cl5WriteGuardian ()
+{
+ int rc;
+ PRFileDesc *file;
+ char fName [MAXPATHLEN + 1];
+ char version [VERSION_SIZE];
+ PRInt32 len, size;
+
+ PR_snprintf (fName, MAXPATHLEN, "%s/%s", s_cl5Desc.dbDir, GUARDIAN_FILE);
+
+ file = PR_Open (fName, PR_WRONLY | PR_CREATE_FILE, s_cl5Desc.dbConfig.fileMode);
+ if (file == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5WriteGuardian: failed to open guardian file; NSPR error - %d\n",
+ PR_GetError());
+ return CL5_SYSTEM_ERROR;
+ }
+
+ PR_snprintf (version, VERSION_SIZE, "%s/%s/%s\n", CL5_TYPE, REPL_PLUGIN_NAME,
+ CHANGELOG_DB_VERSION);
+
+ len = strlen (version);
+ size = slapi_write_buffer (file, version, len);
+ if (size != len)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5WriteGuardian: failed to write guardian file; NSPR error - %d\n",
+ PR_GetError());
+ PR_Close (file);
+ return CL5_SYSTEM_ERROR;
+ }
+
+ rc = PR_Close (file);
+ if (rc != PR_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5WriteGuardian: failed to close guardian file; NSPR error - %d\n",
+ PR_GetError());
+ return CL5_SYSTEM_ERROR;
+ }
+
+ return CL5_SUCCESS;
+}
+
+static int _cl5ReadGuardian (char *buff)
+{
+ int rc;
+ PRFileDesc *file;
+ char fName [MAXPATHLEN + 1];
+ PRInt32 size;
+
+ PR_snprintf (fName, MAXPATHLEN, "%s/%s", s_cl5Desc.dbDir, GUARDIAN_FILE);
+
+ file = PR_Open (fName, PR_RDONLY, 0);
+ if (file == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5ReadGuardian: failed to open guardian file; NSPR error - %d\n",
+ PR_GetError());
+ return CL5_SYSTEM_ERROR;
+ }
+
+ size = slapi_read_buffer (file, buff, VERSION_SIZE);
+ if (size <= 0)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5ReadGuardian: failed to read guardian file; NSPR error - %d\n",
+ PR_GetError());
+ PR_Close (file);
+ return CL5_SYSTEM_ERROR;
+ }
+
+ buff [size-1] = '\0';
+
+ rc = PR_Close (file);
+ if (rc != PR_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5ReadGuardian: failed to close guardian file; NSPR error - %d\n",
+ PR_GetError());
+ return CL5_SYSTEM_ERROR;
+ }
+
+ return CL5_SUCCESS;
+}
+
+static int _cl5RemoveGuardian ()
+{
+ char fName [MAXPATHLEN + 1];
+ int rc;
+
+ PR_snprintf (fName, MAXPATHLEN, "%s/%s", s_cl5Desc.dbDir, GUARDIAN_FILE);
+
+ rc = PR_Delete (fName);
+ if (rc != PR_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5RemoveGuardian: failed to remove guardian file; NSPR error - %d\n",
+ PR_GetError());
+ return CL5_SYSTEM_ERROR;
+ }
+
+ return CL5_SUCCESS;
+}
+
+/* must be called under the state lock */
+static void _cl5Close ()
+{
+ int rc2 = 0;
+ PRIntervalTime interval;
+
+ if (s_cl5Desc.dbState != CL5_STATE_CLOSED) /* Don't try to close twice */
+ {
+
+ /* close db files */
+ _cl5DBClose ();
+
+ /* stop global threads */
+ interval = PR_MillisecondsToInterval(100);
+ while (s_cl5Desc.threadCount > 0)
+ {
+ slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name_cl,
+ "_cl5Close: waiting for threads to exit: %d thread(s) still active\n",
+ s_cl5Desc.threadCount);
+ DS_Sleep(interval);
+ }
+
+ /* cleanup trimming */
+ _cl5TrimCleanup ();
+
+ /* shutdown db environment */
+ if (s_cl5Desc.dbEnv)
+ {
+ DB_ENV *dbEnv = s_cl5Desc.dbEnv;
+ rc2 = dbEnv->close(dbEnv, 0);
+ s_cl5Desc.dbEnv = NULL;
+ }
+
+ /* record successful close by writing guardian file;
+ we do it in all case accept incomplete open due to an error */
+ if (s_cl5Desc.dbState == CL5_STATE_CLOSING || s_cl5Desc.dbOpenMode != CL5_OPEN_NORMAL)
+ {
+ _cl5WriteGuardian ();
+ }
+
+ /* remove changelog if requested */
+ if (s_cl5Desc.dbRmOnClose)
+ {
+
+ if (_cl5Delete (s_cl5Desc.dbDir, 1) != CL5_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "cl5Close: failed to remove changelog\n");
+ }
+ s_cl5Desc.dbRmOnClose = PR_FALSE;
+ }
+
+ slapi_ch_free ((void **)&s_cl5Desc.dbDir);
+ memset (&s_cl5Desc.dbConfig, 0, sizeof (s_cl5Desc.dbConfig));
+ s_cl5Desc.fatalError = PR_FALSE;
+ s_cl5Desc.threadCount = 0;
+ s_cl5Desc.dbOpenMode = CL5_OPEN_NONE;
+ }
+}
+
+static void _cl5DBClose ()
+{
+ if (NULL != s_cl5Desc.dbFiles)
+ {
+ objset_delete (&s_cl5Desc.dbFiles);
+ }
+ if (NULL != s_cl5Desc.fileLock)
+ {
+ PR_DestroyLock (s_cl5Desc.fileLock);
+ }
+}
+
+/* state lock must be locked */
+static int _cl5Delete (const char *clDir, int rmDir)
+{
+ PRDir *dir;
+ char filename[MAXPATHLEN + 1];
+ PRDirEntry *entry = NULL;
+ int rc;
+
+ /* remove all files in the directory and the directory */
+ dir = PR_OpenDir(clDir);
+ if (dir == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5Delete: failed to open changelog dir; NSPR error - %d\n",
+ PR_GetError ());
+ return CL5_SYSTEM_ERROR;
+
+ }
+
+ while (NULL != (entry = PR_ReadDir(dir, PR_SKIP_DOT | PR_SKIP_DOT_DOT)))
+ {
+ if (NULL == entry->name)
+ {
+ break;
+ }
+ PR_snprintf(filename, MAXPATHLEN, "%s/%s", clDir, entry->name);
+ rc = PR_Delete(filename);
+ if (rc != PR_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5Delete: failed to remove (%s) file; NSPR error - %d\n",
+ filename, PR_GetError ());
+ }
+ }
+
+ rc = PR_CloseDir(dir);
+ if (rc != PR_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5Delete: failed to close changelog dir (%s); NSPR error - %d\n",
+ clDir, PR_GetError ());
+ return CL5_SYSTEM_ERROR;
+ }
+
+ if (rmDir)
+ {
+ rc = PR_RmDir (clDir);
+ if (rc != 0)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5Delete: failed to remove changelog dir (%s); errno = %d\n",
+ clDir, errno);
+ return CL5_SYSTEM_ERROR;
+ }
+ }
+
+ return CL5_SUCCESS;
+}
+
+static void _cl5SetDefaultDBConfig ()
+{
+ s_cl5Desc.dbConfig.cacheSize = CL5_DEFAULT_CONFIG_DB_DBCACHESIZE;
+ s_cl5Desc.dbConfig.durableTrans = CL5_DEFAULT_CONFIG_DB_DURABLE_TRANSACTIONS;
+ s_cl5Desc.dbConfig.checkpointInterval = CL5_DEFAULT_CONFIG_DB_CHECKPOINT_INTERVAL;
+ s_cl5Desc.dbConfig.circularLogging = CL5_DEFAULT_CONFIG_DB_CIRCULAR_LOGGING;
+ s_cl5Desc.dbConfig.pageSize = CL5_DEFAULT_CONFIG_DB_PAGE_SIZE;
+ s_cl5Desc.dbConfig.logfileSize = CL5_DEFAULT_CONFIG_DB_LOGFILE_SIZE;
+ s_cl5Desc.dbConfig.maxTxnSize = CL5_DEFAULT_CONFIG_DB_TXN_MAX;
+ s_cl5Desc.dbConfig.verbose = CL5_DEFAULT_CONFIG_DB_VERBOSE;
+ s_cl5Desc.dbConfig.debug = CL5_DEFAULT_CONFIG_DB_DEBUG;
+ s_cl5Desc.dbConfig.tricklePercentage = CL5_DEFAULT_CONFIG_DB_TRICKLE_PERCENTAGE;
+ s_cl5Desc.dbConfig.spinCount = CL5_DEFAULT_CONFIG_DB_SPINCOUNT;
+ s_cl5Desc.dbConfig.nb_lock_config = CL5_DEFAULT_CONFIG_NB_LOCK;
+ s_cl5Desc.dbConfig.fileMode = FILE_CREATE_MODE;
+}
+
+static void _cl5SetDBConfig (const CL5DBConfig *config)
+{
+ /* through CL5DBConfig, we have access to all the LDAP configurable Changelog DB parameters */
+ s_cl5Desc.dbConfig.cacheSize = config->cacheSize;
+ s_cl5Desc.dbConfig.durableTrans = config->durableTrans;
+ s_cl5Desc.dbConfig.checkpointInterval = config->checkpointInterval;
+ s_cl5Desc.dbConfig.circularLogging = config->circularLogging;
+ s_cl5Desc.dbConfig.pageSize = config->pageSize;
+ s_cl5Desc.dbConfig.logfileSize = config->logfileSize;
+ s_cl5Desc.dbConfig.maxTxnSize = config->maxTxnSize;
+ s_cl5Desc.dbConfig.verbose = config->verbose;
+ s_cl5Desc.dbConfig.debug = config->debug;
+ s_cl5Desc.dbConfig.tricklePercentage = config->tricklePercentage;
+ s_cl5Desc.dbConfig.spinCount = config->spinCount;
+ s_cl5Desc.dbConfig.nb_lock_config = config->nb_lock_config;
+ s_cl5Desc.dbConfig.maxConcurrentWrites = config->maxConcurrentWrites;
+
+ if (config->spinCount != 0)
+ {
+ DB_ENV_SET_TAS_SPINS(s_cl5Desc.dbEnv, config->spinCount);
+ }
+
+ /* Some other configuration parameters are hardcoded... */
+ s_cl5Desc.dbConfig.fileMode = FILE_CREATE_MODE;
+}
+
+#define ONEG 1073741824 /* one giga bytes */
+static void _cl5InitDBEnv(DB_ENV *dbEnv)
+{
+ dbEnv->set_errpfx(dbEnv, "ns-slapd");
+ dbEnv->set_lg_max(dbEnv, s_cl5Desc.dbConfig.logfileSize);
+ dbEnv->set_tx_max(dbEnv, s_cl5Desc.dbConfig.maxTxnSize);
+ dbEnv->set_cachesize(dbEnv, s_cl5Desc.dbConfig.cacheSize/ONEG,
+ s_cl5Desc.dbConfig.cacheSize%ONEG,
+ 0);
+ /* Set default number of locks */
+ dbEnv->set_lk_max_locks(dbEnv, s_cl5Desc.dbConfig.nb_lock_config);
+
+ if (s_cl5Desc.dbConfig.verbose)
+ {
+ int on = 1;
+ dbEnv->set_verbose(dbEnv, DB_VERB_CHKPOINT, on);
+ dbEnv->set_verbose(dbEnv, DB_VERB_DEADLOCK, on);
+ dbEnv->set_verbose(dbEnv, DB_VERB_RECOVERY, on);
+ dbEnv->set_verbose(dbEnv, DB_VERB_WAITSFOR, on);
+ }
+ if (s_cl5Desc.dbConfig.debug)
+ {
+ dbEnv->set_errcall(dbEnv, _cl5DBLogPrint);
+ }
+#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR >= 3300
+ dbEnv->set_alloc(dbEnv, malloc, realloc, free);
+#endif
+}
+
+static void _cl5DBLogPrint(const char* prefix, char *buffer)
+{
+ /* We ignore the prefix since we know who we are anyway */
+ slapi_log_error (SLAPI_LOG_FATAL, repl_plugin_name_cl, "cl5: %s\n", buffer);
+}
+
+static PRBool _cl5IsLogFile (const char *path)
+{
+ int rc;
+
+ /* Is the filename at least 4 characters long ? */
+ if (strlen(path) < 4)
+ {
+ return PR_FALSE; /* Not a log file then */
+ }
+
+ /* Are the first 4 characters "log." ? */
+ rc = strncmp(path,"log.",4);
+ if (0 == rc)
+ {
+ /* Now, are the last 4 characters _not_ .db# ? */
+ const char *piece = path + (strlen(path) - 4);
+ rc = strcmp(piece, DB_EXTENSION);
+ if (0 != rc)
+ {
+ /* Is */
+ return PR_TRUE;
+ }
+ }
+ return PR_FALSE; /* Is not */
+}
+
+static int _cl5Recover (int open_flags, DB_ENV *dbEnv)
+{
+ /* If we're doing recovery, we MUST open the env single-threaded ! */
+ int recover_flags = open_flags & ~DB_THREAD;
+ int rc;
+
+ rc = dbEnv->open(dbEnv, s_cl5Desc.dbDir, recover_flags, s_cl5Desc.dbConfig.fileMode);
+
+ if (rc != 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5Recover: appinit failed; db error - %d %s\n",
+ rc, db_strerror(rc));
+ return CL5_DB_ERROR;
+ }
+
+ /* Now close it so we can re-open it again... */
+ dbEnv->close(dbEnv, 0);
+
+ return CL5_SUCCESS;
+}
+
+/* Trimming helper functions */
+static int _cl5TrimInit ()
+{
+ /* just create the lock while we are singlethreaded */
+ s_cl5Desc.dbTrim.lock = PR_NewLock();
+
+ if (s_cl5Desc.dbTrim.lock == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5InitTrimming: failed to create lock; NSPR error - %d\n",
+ PR_GetError ());
+ return CL5_SYSTEM_ERROR;
+ }
+ else
+ {
+ return CL5_SUCCESS;
+ }
+}
+
+static void _cl5TrimCleanup ()
+{
+ if (s_cl5Desc.dbTrim.lock)
+ PR_DestroyLock (s_cl5Desc.dbTrim.lock);
+
+ memset (&s_cl5Desc.dbTrim, 0, sizeof (s_cl5Desc.dbTrim));
+}
+
+static int _cl5TrimMain (void *param)
+{
+ PRIntervalTime interval;
+ time_t timePrev = current_time ();
+ time_t timeNow;
+
+ PR_AtomicIncrement (&s_cl5Desc.threadCount);
+ interval = PR_SecondsToInterval(CHANGELOGDB_TRIM_INTERVAL);
+
+ while (s_cl5Desc.dbState != CL5_STATE_CLOSING)
+ {
+ timeNow = current_time ();
+ if (timeNow - timePrev >= CHANGELOGDB_TRIM_INTERVAL)
+ {
+ /* time to trim */
+ timePrev = timeNow;
+ _cl5DoTrimming ();
+ }
+ if (NULL == s_cl5Desc.clLock)
+ {
+ /* most likely, emergency */
+ break;
+ }
+
+ PR_Lock(s_cl5Desc.clLock);
+ PR_WaitCondVar(s_cl5Desc.clCvar, interval);
+ PR_Unlock(s_cl5Desc.clLock);
+ }
+
+ PR_AtomicDecrement (&s_cl5Desc.threadCount);
+
+ return 0;
+}
+
+/* We remove an entry if it has been replayed to all consumers and
+ and the number of entries in the changelog is larger than maxEntries
+ or age of the entry is larger than maxAge.
+ Also we can't purge entries which correspond to max csns in the
+ supplier's ruv. Here is a example where we can get into trouble:
+ The server is setup with time based trimming and no consumer's
+ At some point all the entries are trimmed from the changelog.
+ At a later point a consumer is added and initialized online
+ Then a change is made on the supplier.
+ To update the consumer, the supplier would attempt to locate
+ the last change sent to the consumer in the changelog and will
+ fail because the change was removed.
+
+ */
+
+static void _cl5DoTrimming ()
+{
+ Object *obj;
+ long numToTrim;
+
+ PR_Lock (s_cl5Desc.dbTrim.lock);
+
+ /* ONREPL We trim file by file which means that some files will be
+ trimmed more often than other. We might have to fix that by, for
+ example, randomizing starting point */
+ obj = objset_first_obj (s_cl5Desc.dbFiles);
+ while (obj && _cl5CanTrim ((time_t)0, &numToTrim))
+ {
+ _cl5TrimFile (obj, &numToTrim);
+ obj = objset_next_obj (s_cl5Desc.dbFiles, obj);
+ }
+
+ if (obj)
+ object_release (obj);
+
+ PR_Unlock (s_cl5Desc.dbTrim.lock);
+
+ return;
+}
+
+/* Note that each file contains changes for a single replicated area.
+ trimming algorithm:
+*/
+#define CL5_TRIM_MAX_PER_TRANSACTION 10
+
+static void _cl5TrimFile (Object *obj, long *numToTrim)
+{
+ DB_TXN *txnid;
+ RUV *ruv = NULL;
+ CL5Entry entry;
+ slapi_operation_parameters op = {0};
+ void *it;
+ int finished = 0, totalTrimmed = 0, count;
+ PRBool abort;
+ char strCSN[CSN_STRSIZE];
+ int rc;
+
+ PR_ASSERT (obj);
+
+ /* construct the ruv up to which we can purge */
+ rc = _cl5GetRUV2Purge2 (obj, &ruv);
+ if (rc != CL5_SUCCESS || ruv == NULL)
+ {
+ return;
+ }
+
+ entry.op = &op;
+
+ while ( !finished && !g_get_shutdown() )
+ {
+ it = NULL;
+ count = 0;
+ txnid = NULL;
+ abort = PR_FALSE;
+
+ /* DB txn lock accessed pages until the end of the transaction. */
+
+ rc = TXN_BEGIN(s_cl5Desc.dbEnv, NULL, &txnid, 0);
+ if (rc != 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5TrimFile: failed to begin transaction; db error - %d %s\n",
+ rc, db_strerror(rc));
+ finished = PR_TRUE;
+ break;
+ }
+
+ finished = _cl5GetFirstEntry (obj, &entry, &it, txnid);
+ while ( !finished )
+ {
+ /*
+ * This change can be trimmed if it exceeds purge
+ * parameters and has been seen by all consumers.
+ */
+ if ( (*numToTrim > 0 || _cl5CanTrim (entry.time, numToTrim)) &&
+ ruv_covers_csn_strict (ruv, op.csn) )
+ {
+ rc = _cl5CurrentDeleteEntry (it);
+ if ( rc == CL5_SUCCESS )
+ {
+ /* update purge vector */
+ rc = _cl5UpdateRUV (obj, op.csn, PR_FALSE, PR_TRUE);
+ }
+ if ( rc == CL5_SUCCESS)
+ {
+ if (*numToTrim > 0) (*numToTrim)--;
+ count++;
+ }
+ else
+ {
+ /* The above two functions have logged the error */
+ abort = PR_TRUE;
+ }
+
+ }
+ else
+ {
+ /* The changelog DB is time ordered. If we can not trim
+ * a CSN, we will not be allowed to trim the rest of the
+ * CSNs generally. However, the maxcsn of each replica ID
+ * is always kept in the changelog as an anchor for
+ * replaying future changes. We have to skip those anchor
+ * CSNs, otherwise a non-active replica ID could block
+ * the trim forever.
+ */
+ CSN *maxcsn = NULL;
+ ReplicaId rid;
+
+ rid = csn_get_replicaid (op.csn);
+ ruv_get_largest_csn_for_replica (ruv, rid, &maxcsn);
+ if ( csn_compare (op.csn, maxcsn) != 0 )
+ {
+ /* op.csn is not anchor CSN */
+ finished = 1;
+ }
+ else
+ {
+ slapi_log_error (SLAPI_LOG_REPL, NULL,
+ "Changelog purge skipped anchor csn %s\n",
+ csn_as_string (maxcsn, PR_FALSE, strCSN));
+
+ /* extra read to skip the current record */
+ cl5_operation_parameters_done (&op);
+ finished =_cl5GetNextEntry (&entry, it);
+ }
+ if (maxcsn) csn_free (&maxcsn);
+ }
+ cl5_operation_parameters_done (&op);
+ if (finished || abort || count >= CL5_TRIM_MAX_PER_TRANSACTION)
+ {
+ /* If we reach CL5_TRIM_MAX_PER_TRANSACTION,
+ * we close the cursor,
+ * commit the transaction and restart a new transaction
+ */
+ break;
+ }
+ finished = _cl5GetNextEntry (&entry, it);
+ }
+
+ /* MAB: We need to close the cursor BEFORE the txn commits/aborts.
+ * If we don't respect this order, we'll screw up the database,
+ * placing it in DB_RUNRECOVERY mode
+ */
+ cl5DestroyIterator (it);
+
+ if (abort)
+ {
+ finished = 1;
+ rc = TXN_ABORT (txnid);
+ if (rc != 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5TrimFile: failed to abort transaction; db error - %d %s\n",
+ rc, db_strerror(rc));
+ }
+ }
+ else
+ {
+ rc = TXN_COMMIT (txnid, 0);
+ if (rc != 0)
+ {
+ finished = 1;
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5TrimFile: failed to commit transaction; db error - %d %s\n",
+ rc, db_strerror(rc));
+ }
+ else
+ {
+ totalTrimmed += count;
+ }
+ }
+
+ } /* While (!finished) */
+
+ if (ruv)
+ ruv_destroy (&ruv);
+
+ if (totalTrimmed)
+ {
+ slapi_log_error (SLAPI_LOG_REPL, NULL, "Trimmed %d changes from the changelog\n", totalTrimmed);
+ }
+}
+
+static PRBool _cl5CanTrim (time_t time, long *numToTrim)
+{
+ *numToTrim = 0;
+
+ if (s_cl5Desc.dbTrim.maxAge == 0 && s_cl5Desc.dbTrim.maxEntries == 0)
+ return PR_FALSE;
+
+ if (s_cl5Desc.dbTrim.maxAge == 0)
+ {
+ *numToTrim = cl5GetOperationCount (NULL) - s_cl5Desc.dbTrim.maxEntries;
+ return ( *numToTrim > 0 );
+ }
+
+ if (s_cl5Desc.dbTrim.maxEntries > 0 &&
+ (*numToTrim = cl5GetOperationCount (NULL) - s_cl5Desc.dbTrim.maxEntries) > 0)
+ return PR_TRUE;
+
+ if (time)
+ return (current_time () - time > s_cl5Desc.dbTrim.maxAge);
+ else
+ return PR_TRUE;
+}
+
+static int _cl5ReadRUV (const char *replGen, Object *obj, PRBool purge)
+{
+ int rc;
+ char csnStr [CSN_STRSIZE];
+ DBT key={0}, data={0};
+ struct berval **vals;
+ CL5DBFile *file;
+ char *pos;
+ char *agmt_name;
+
+
+ PR_ASSERT (replGen && obj);
+
+ file = (CL5DBFile*)object_get_data (obj);
+ PR_ASSERT (file);
+
+ agmt_name = get_thread_private_agmtname();
+
+ if (purge) /* read purge vector entry */
+ key.data = _cl5GetHelperEntryKey (PURGE_RUV_TIME, csnStr);
+ else /* read upper bound vector */
+ key.data = _cl5GetHelperEntryKey (MAX_RUV_TIME, csnStr);
+
+ key.size = CSN_STRSIZE;
+
+ data.flags = DB_DBT_MALLOC;
+
+ rc = file->db->get(file->db, NULL/*txn*/, &key, &data, 0);
+ switch (rc)
+ {
+ case 0: pos = data.data;
+ rc = _cl5ReadBervals (&vals, &pos, data.size);
+ free (data.data);
+ if (rc != CL5_SUCCESS)
+ return rc;
+
+ if (purge)
+ rc = ruv_init_from_bervals(vals, &file->purgeRUV);
+ else
+ rc = ruv_init_from_bervals(vals, &file->maxRUV);
+
+ if (rc != RUV_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "%s: _cl5ReadRUV: failed to initialize %s ruv; "
+ "RUV error %d\n", agmt_name, purge? "purge" : "upper bound", rc);
+
+ return CL5_RUV_ERROR;
+ }
+
+ ber_bvecfree(vals);
+
+ /* delete the entry; it is re-added when file
+ is successfully closed */
+ file->db->del (file->db, NULL, &key, DEFAULT_DB_OP_FLAGS);
+
+ return CL5_SUCCESS;
+
+ case DB_NOTFOUND: /* RUV is lost - need to construct */
+ rc = _cl5ConstructRUV (replGen, obj, purge);
+ return rc;
+
+ default: slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "%s: _cl5ReadRUV: failed to get purge RUV; "
+ "db error - %d %s\n", agmt_name, rc, db_strerror(rc));
+ return CL5_DB_ERROR;
+ }
+}
+
+static int _cl5WriteRUV (CL5DBFile *file, PRBool purge)
+{
+ int rc;
+ DBT key={0}, data={0};
+ char csnStr [CSN_STRSIZE];
+ struct berval **vals;
+ DB_TXN *txnid = NULL;
+
+ if ((purge && file->purgeRUV == NULL) || (!purge && file->maxRUV == NULL))
+ return CL5_SUCCESS;
+
+ if (purge)
+ {
+ key.data = _cl5GetHelperEntryKey (PURGE_RUV_TIME, csnStr);
+ rc = ruv_to_bervals(file->purgeRUV, &vals);
+ }
+ else
+ {
+ key.data = _cl5GetHelperEntryKey (MAX_RUV_TIME, csnStr);
+ rc = ruv_to_bervals(file->maxRUV, &vals);
+ }
+
+ key.size = CSN_STRSIZE;
+
+ rc = _cl5WriteBervals (vals, (char**)&data.data, &data.size);
+ ber_bvecfree(vals);
+ if (rc != CL5_SUCCESS)
+ {
+ return rc;
+ }
+
+#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100
+ rc = txn_begin(s_cl5Desc.dbEnv, NULL, &txnid, 0);
+ if (rc != 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5WriteRUV: failed to begin transaction; db error - %d %s\n",
+ rc, db_strerror(rc));
+ return CL5_DB_ERROR;
+ }
+#endif
+ rc = file->db->put(file->db, txnid, &key, &data, DEFAULT_DB_OP_FLAGS);
+
+ slapi_ch_free ((void**)&data.data);
+ if ( rc == 0 )
+ {
+#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100
+ rc = txn_commit (txnid, 0);
+ if (rc != 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5WriteRUV: failed to commit transaction; db error - %d %s\n",
+ rc, db_strerror(rc));
+ return CL5_DB_ERROR;
+ }
+#endif
+ return CL5_SUCCESS;
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5WriteRUV: failed to write %s RUV for file %s; db error - %d\n",
+ purge? "purge" : "upper bound", file->name, rc);
+
+ if (CL5_OS_ERR_IS_DISKFULL(rc))
+ {
+ cl5_set_diskfull();
+ return CL5_DB_ERROR;
+ }
+#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100
+ rc = txn_abort (txnid);
+ if (rc != 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5WriteRUV: failed to abort transaction; db error - %d %s\n",
+ rc, db_strerror(rc));
+ }
+#endif
+ return CL5_DB_ERROR;
+ }
+}
+
+/* This is a very slow process since we have to read every changelog entry.
+ Hopefully, this function is not called too often */
+static int _cl5ConstructRUV (const char *replGen, Object *obj, PRBool purge)
+{
+ int rc;
+ CL5Entry entry;
+ void *iterator = NULL;
+ slapi_operation_parameters op = {0};
+ CL5DBFile *file;
+
+ PR_ASSERT (replGen && obj);
+
+ file = (CL5DBFile*)object_get_data (obj);
+ PR_ASSERT (file);
+
+ /* construct the RUV */
+ if (purge)
+ rc = ruv_init_new (replGen, 0, NULL, &file->purgeRUV);
+ else
+ rc = ruv_init_new (replGen, 0, NULL, &file->maxRUV);
+ if (rc != RUV_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5ConstructRUV: "
+ "failed to initialize %s RUV for file %s; ruv error - %d\n",
+ purge? "purge" : "upper bound", file->name, rc);
+ return CL5_RUV_ERROR;
+ }
+
+ entry.op = &op;
+ rc = _cl5GetFirstEntry (obj, &entry, &iterator, NULL);
+ while (rc == CL5_SUCCESS)
+ {
+ if (purge)
+ rc = ruv_set_csns_keep_smallest(file->purgeRUV, op.csn);
+ else
+ rc = ruv_set_csns (file->maxRUV, op.csn, NULL);
+
+ cl5_operation_parameters_done (&op);
+ if (rc != RUV_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5ConstructRUV: "
+ "failed to updated %s RUV for file %s; ruv error - %d\n",
+ purge ? "purge" : "upper bound", file->name, rc);
+ rc = CL5_RUV_ERROR;
+ continue;
+ }
+
+ rc = _cl5GetNextEntry (&entry, iterator);
+ }
+
+ cl5_operation_parameters_done (&op);
+
+ if (iterator)
+ cl5DestroyIterator (iterator);
+
+ if (rc == CL5_NOTFOUND)
+ {
+ rc = CL5_SUCCESS;
+ }
+ else
+ {
+ if (purge)
+ ruv_destroy (&file->purgeRUV);
+ else
+ ruv_destroy (&file->maxRUV);
+ }
+
+ return rc;
+}
+
+static int _cl5UpdateRUV (Object *obj, CSN *csn, PRBool newReplica, PRBool purge)
+{
+ ReplicaId rid;
+ int rc = RUV_SUCCESS; /* initialize rc to avoid erroneous logs */
+ CL5DBFile *file;
+
+ PR_ASSERT (obj && csn);
+
+ file = (CL5DBFile*)object_get_data (obj);
+
+ /* if purge is TRUE, file->purgeRUV must be set;
+ if purge is FALSE, maxRUV must be set */
+ PR_ASSERT (file && ((purge && file->purgeRUV) || (!purge && file->maxRUV)));
+
+ /* update vector only if this replica is not yet part of RUV */
+ if (purge && newReplica)
+ {
+ rid = csn_get_replicaid(csn);
+ if (ruv_contains_replica (file->purgeRUV, rid))
+ return CL5_SUCCESS;
+ else
+ {
+ /* if the replica is not part of the purgeRUV yet, add it */
+ ruv_add_replica (file->purgeRUV, rid, multimaster_get_local_purl());
+ }
+ }
+ else
+ {
+ if (purge)
+ rc = ruv_set_csns(file->purgeRUV, csn, NULL);
+ else
+ rc = ruv_set_csns(file->maxRUV, csn, NULL);
+ }
+
+ if (rc != RUV_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5UpdatePurgeRUV: "
+ "failed to update %s RUV for file %s; ruv error - %d\n",
+ purge ? "purge" : "upper bound", file->name, rc);
+ return CL5_RUV_ERROR;
+ }
+
+ return CL5_SUCCESS;
+}
+
+static int _cl5EnumConsumerRUV (const ruv_enum_data *element, void *arg)
+{
+ int rc;
+ RUV *ruv;
+ CSN *csn = NULL;
+
+ PR_ASSERT (element && element->csn && arg);
+
+ ruv = (RUV*)arg;
+
+ rc = ruv_get_largest_csn_for_replica(ruv, csn_get_replicaid (element->csn), &csn);
+ if (rc != RUV_SUCCESS || csn == NULL || csn_compare (element->csn, csn) < 0)
+ {
+ ruv_set_max_csn(ruv, element->csn, NULL);
+ }
+
+ if (csn)
+ csn_free (&csn);
+
+ return 0;
+}
+
+static int _cl5GetRUV2Purge2 (Object *fileObj, RUV **ruv)
+{
+ int rc = CL5_SUCCESS;
+ CL5DBFile *dbFile;
+ Object *rObj = NULL;
+ Replica *r = NULL;
+ Object *agmtObj = NULL;
+ Repl_Agmt *agmt;
+ Object *consRUVObj, *supRUVObj;
+ RUV *consRUV, *supRUV;
+ CSN *csn;
+
+ PR_ASSERT (fileObj && ruv);
+
+ dbFile = (CL5DBFile*)object_get_data (fileObj);
+ PR_ASSERT (dbFile);
+
+ rObj = replica_get_by_name (dbFile->replName);
+ PR_ASSERT (rObj);
+ r = (Replica*)object_get_data (rObj);
+ PR_ASSERT (r);
+
+ /* We start with this replica's RUV. See note in _cl5DoTrimming */
+ supRUVObj = replica_get_ruv (r);
+ PR_ASSERT (supRUVObj);
+
+ supRUV = (RUV*)object_get_data (supRUVObj);
+ PR_ASSERT (supRUV);
+
+ *ruv = ruv_dup (supRUV);
+
+ object_release (supRUVObj);
+
+ agmtObj = agmtlist_get_first_agreement_for_replica (r);
+ while (agmtObj)
+ {
+ agmt = (Repl_Agmt*)object_get_data (agmtObj);
+ PR_ASSERT (agmt);
+
+ consRUVObj = agmt_get_consumer_ruv (agmt);
+ if (consRUVObj)
+ {
+ consRUV = (RUV*)object_get_data (consRUVObj);
+ rc = ruv_enumerate_elements (consRUV, _cl5EnumConsumerRUV, *ruv);
+ if (rc != RUV_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5GetRUV2Purge2: "
+ "failed to construct ruv; ruv error - %d\n", rc);
+ rc = CL5_RUV_ERROR;
+ object_release (consRUVObj);
+ object_release (agmtObj);
+ break;
+ }
+
+ object_release (consRUVObj);
+ }
+
+ agmtObj = agmtlist_get_next_agreement_for_replica (r, agmtObj);
+ }
+
+ /* check if there is any data in the constructed ruv - otherwise get rid of it */
+ if (ruv_get_max_csn(*ruv, &csn) != RUV_SUCCESS || csn == NULL)
+ {
+ ruv_destroy (ruv);
+ }
+ else
+ {
+ csn_free (&csn);
+ }
+
+ if (rObj)
+ object_release (rObj);
+
+ if (rc != CL5_SUCCESS && ruv)
+ ruv_destroy (ruv);
+
+ return rc;
+}
+
+static int _cl5GetEntryCount (CL5DBFile *file)
+{
+ int rc;
+ char csnStr [CSN_STRSIZE];
+ DBT key={0}, data={0};
+ DB_BTREE_STAT *stats = NULL;
+
+ PR_ASSERT (file);
+
+ /* read entry count. if the entry is there - the file was successfully closed
+ last time it was used */
+ key.data = _cl5GetHelperEntryKey (ENTRY_COUNT_TIME, csnStr);
+ key.size = CSN_STRSIZE;
+
+ data.flags = DB_DBT_MALLOC;
+
+ rc = file->db->get(file->db, NULL/*txn*/, &key, &data, 0);
+ switch (rc)
+ {
+ case 0: file->entryCount = *(int*)data.data;
+ free (data.data);
+
+ /* delete the entry. the entry is re-added when file
+ is successfully closed */
+ file->db->del (file->db, NULL, &key, DEFAULT_DB_OP_FLAGS);
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5GetEntryCount: %d changes for replica %s\n",
+ file->entryCount, file->replName);
+ return CL5_SUCCESS;
+
+ case DB_NOTFOUND: file->entryCount = 0;
+#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR >= 3300
+ rc = file->db->stat(file->db, (void*)&stats, 0);
+#else
+ rc = file->db->stat(file->db, (void*)&stats, malloc, 0);
+#endif
+ if (rc != 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5GetEntryCount: failed to get changelog statistics; "
+ "db error - %d %s\n", rc, db_strerror(rc));
+ return CL5_DB_ERROR;
+ }
+
+#ifdef DB30
+ file->entryCount = stats->bt_nrecs;
+#else /* DB31 */
+ file->entryCount = stats->bt_ndata;
+#endif
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5GetEntryCount: %d changes for replica %s\n",
+ file->entryCount, file->replName);
+
+ free (stats);
+ return CL5_SUCCESS;
+
+ default: slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5GetEntryCount: failed to get count entry; "
+ "db error - %d %s\n", rc, db_strerror(rc));
+ return CL5_DB_ERROR;
+ }
+}
+
+static int _cl5WriteEntryCount (CL5DBFile *file)
+{
+ int rc;
+ DBT key={0}, data={0};
+ char csnStr [CSN_STRSIZE];
+ DB_TXN *txnid = NULL;
+
+ key.data = _cl5GetHelperEntryKey (ENTRY_COUNT_TIME, csnStr);
+ key.size = CSN_STRSIZE;
+ data.data = (void*)&file->entryCount;
+ data.size = sizeof (file->entryCount);
+
+#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100
+ rc = txn_begin(s_cl5Desc.dbEnv, NULL, &txnid, 0);
+ if (rc != 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5WriteEntryCount: failed to begin transaction; db error - %d %s\n",
+ rc, db_strerror(rc));
+ return CL5_DB_ERROR;
+ }
+#endif
+ rc = file->db->put(file->db, txnid, &key, &data, DEFAULT_DB_OP_FLAGS);
+ if (rc == 0)
+ {
+#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100
+ rc = txn_commit (txnid, 0);
+ if (rc != 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5WriteEntryCount: failed to commit transaction; db error - %d %s\n",
+ rc, db_strerror(rc));
+ return CL5_DB_ERROR;
+ }
+#endif
+ return CL5_SUCCESS;
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5WriteEntryCount: "
+ "failed to write count entry for file %s; db error - %d %s\n",
+ file->name, rc, db_strerror(rc));
+ if (CL5_OS_ERR_IS_DISKFULL(rc))
+ {
+ cl5_set_diskfull();
+ return CL5_DB_ERROR;
+ }
+#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100
+ rc = txn_abort (txnid);
+ if (rc != 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5WriteEntryCount: failed to abort transaction; db error - %d %s\n",
+ rc, db_strerror(rc));
+ }
+#endif
+ return CL5_DB_ERROR;
+ }
+}
+
+static const char* _cl5OperationType2Str (int type)
+{
+ switch (type)
+ {
+ case SLAPI_OPERATION_ADD: return T_ADDCTSTR;
+ case SLAPI_OPERATION_MODIFY: return T_MODIFYCTSTR;
+ case SLAPI_OPERATION_MODRDN: return T_MODRDNCTSTR;
+ case SLAPI_OPERATION_DELETE: return T_DELETECTSTR;
+ default: return NULL;
+ }
+}
+
+static int _cl5Str2OperationType (const char *str)
+{
+ if (strcasecmp (str, T_ADDCTSTR) == 0)
+ return SLAPI_OPERATION_ADD;
+
+ if (strcasecmp (str, T_MODIFYCTSTR) == 0)
+ return SLAPI_OPERATION_MODIFY;
+
+ if (strcasecmp (str, T_MODRDNCTSTR) == 0)
+ return SLAPI_OPERATION_MODRDN;
+
+ if (strcasecmp (str, T_DELETECTSTR) == 0)
+ return SLAPI_OPERATION_DELETE;
+
+ return -1;
+}
+
+static int _cl5Operation2LDIF (const slapi_operation_parameters *op, const char *replGen,
+ char **ldifEntry, PRInt32 *lenLDIF)
+{
+ int len = 2;
+ lenstr *l = NULL;
+ const char *strType;
+ char *strDeleteOldRDN;
+ char *buff, *start;
+ LDAPMod **add_mods;
+ char *rawDN;
+ char strCSN[CSN_STRSIZE];
+
+ PR_ASSERT (op && replGen && ldifEntry && IsValidOperation (op));
+
+ strType = _cl5OperationType2Str (op->operation_type);
+ csn_as_string(op->csn,PR_FALSE,strCSN);
+
+ /* find length of the buffer */
+ len += LDIF_SIZE_NEEDED(strlen (T_CHANGETYPESTR), strlen (strType));
+ len += LDIF_SIZE_NEEDED(strlen (T_REPLGEN), strlen (replGen));
+ len += LDIF_SIZE_NEEDED(strlen (T_CSNSTR), strlen (strCSN));
+ len += LDIF_SIZE_NEEDED(strlen (T_UNIQUEIDSTR), strlen (op->target_address.uniqueid));
+
+ switch (op->operation_type)
+ {
+ case SLAPI_OPERATION_ADD: if (op->p.p_add.parentuniqueid)
+ len += LDIF_SIZE_NEEDED(strlen (T_PARENTIDSTR),
+ strlen (op->p.p_add.parentuniqueid));
+ slapi_entry2mods (op->p.p_add.target_entry, &rawDN, &add_mods);
+ len += LDIF_SIZE_NEEDED(strlen (T_DNSTR), strlen (rawDN));
+ l = make_changes_string(add_mods, NULL);
+ len += LDIF_SIZE_NEEDED(strlen (T_CHANGESTR), l->ls_len);
+ ldap_mods_free (add_mods, 1);
+ break;
+
+ case SLAPI_OPERATION_MODIFY: len += LDIF_SIZE_NEEDED(strlen (T_DNSTR), strlen (op->target_address.dn));
+ l = make_changes_string(op->p.p_modify.modify_mods, NULL);
+ len += LDIF_SIZE_NEEDED(strlen (T_CHANGESTR), l->ls_len);
+ break;
+
+ case SLAPI_OPERATION_MODRDN: len += LDIF_SIZE_NEEDED(strlen (T_DNSTR), strlen (op->target_address.dn));
+ len += LDIF_SIZE_NEEDED(strlen (T_NEWRDNSTR),
+ strlen (op->p.p_modrdn.modrdn_newrdn));
+ strDeleteOldRDN = (op->p.p_modrdn.modrdn_deloldrdn ? "true" : "false");
+ len += LDIF_SIZE_NEEDED(strlen (T_DRDNFLAGSTR),
+ strlen (strDeleteOldRDN));
+ if (op->p.p_modrdn.modrdn_newsuperior_address.dn)
+ len += LDIF_SIZE_NEEDED(strlen (T_NEWSUPERIORDNSTR),
+ strlen (op->p.p_modrdn.modrdn_newsuperior_address.dn));
+ if (op->p.p_modrdn.modrdn_newsuperior_address.uniqueid)
+ len += LDIF_SIZE_NEEDED(strlen (T_NEWSUPERIORIDSTR),
+ strlen (op->p.p_modrdn.modrdn_newsuperior_address.uniqueid));
+ l = make_changes_string(op->p.p_modrdn.modrdn_mods, NULL);
+ len += LDIF_SIZE_NEEDED(strlen (T_CHANGESTR), l->ls_len);
+ break;
+
+ case SLAPI_OPERATION_DELETE: len += LDIF_SIZE_NEEDED(strlen (T_DNSTR), strlen (op->target_address.dn));
+ break;
+
+ default: slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5Operation2LDIF: invalid operation type - %d\n", op->operation_type);
+
+ return CL5_BAD_FORMAT;
+ }
+
+ /* allocate buffer */
+ buff = (char*)slapi_ch_malloc (len);
+ start = buff;
+ if (buff == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5Operation2LDIF: memory allocation failed\n");
+ return CL5_MEMORY_ERROR;
+ }
+
+ /* fill buffer */
+ ldif_put_type_and_value(&buff, T_CHANGETYPESTR, (char*)strType, strlen (strType));
+ ldif_put_type_and_value(&buff, T_REPLGEN, (char*)replGen, strlen (replGen));
+ ldif_put_type_and_value(&buff, T_CSNSTR, (char*)strCSN, strlen (strCSN));
+ ldif_put_type_and_value(&buff, T_UNIQUEIDSTR, op->target_address.uniqueid,
+ strlen (op->target_address.uniqueid));
+
+ switch (op->operation_type)
+ {
+ case SLAPI_OPERATION_ADD: if (op->p.p_add.parentuniqueid)
+ ldif_put_type_and_value(&buff, T_PARENTIDSTR,
+ op->p.p_add.parentuniqueid, strlen (op->p.p_add.parentuniqueid));
+ ldif_put_type_and_value(&buff, T_DNSTR, rawDN, strlen (rawDN));
+ ldif_put_type_and_value(&buff, T_CHANGESTR, l->ls_buf, l->ls_len);
+ slapi_ch_free ((void**)&rawDN);
+ break;
+
+ case SLAPI_OPERATION_MODIFY: ldif_put_type_and_value(&buff, T_DNSTR, op->target_address.dn,
+ strlen (op->target_address.dn));
+ ldif_put_type_and_value(&buff, T_CHANGESTR, l->ls_buf, l->ls_len);
+ break;
+
+ case SLAPI_OPERATION_MODRDN: ldif_put_type_and_value(&buff, T_DNSTR, op->target_address.dn,
+ strlen (op->target_address.dn));
+ ldif_put_type_and_value(&buff, T_NEWRDNSTR, op->p.p_modrdn.modrdn_newrdn,
+ strlen (op->p.p_modrdn.modrdn_newrdn));
+ ldif_put_type_and_value(&buff, T_DRDNFLAGSTR, strDeleteOldRDN,
+ strlen (strDeleteOldRDN));
+ if (op->p.p_modrdn.modrdn_newsuperior_address.dn)
+ ldif_put_type_and_value(&buff, T_NEWSUPERIORDNSTR,
+ op->p.p_modrdn.modrdn_newsuperior_address.dn,
+ strlen (op->p.p_modrdn.modrdn_newsuperior_address.dn));
+ if (op->p.p_modrdn.modrdn_newsuperior_address.uniqueid)
+ ldif_put_type_and_value(&buff, T_NEWSUPERIORIDSTR,
+ op->p.p_modrdn.modrdn_newsuperior_address.uniqueid,
+ strlen (op->p.p_modrdn.modrdn_newsuperior_address.uniqueid));
+ ldif_put_type_and_value(&buff, T_CHANGESTR, l->ls_buf, l->ls_len);
+ break;
+
+ case SLAPI_OPERATION_DELETE: ldif_put_type_and_value(&buff, T_DNSTR, op->target_address.dn,
+ strlen (op->target_address.dn));
+ break;
+ }
+
+ *buff = '\n';
+ buff ++;
+ *buff = '\0';
+
+ *ldifEntry = start;
+ *lenLDIF = buff - start;
+
+ if (l)
+ lenstr_free(&l);
+
+ return CL5_SUCCESS;
+}
+
+static int
+_cl5LDIF2Operation (char *ldifEntry, slapi_operation_parameters *op, char **replGen)
+{
+ int rc;
+ int vlen;
+ char *next, *line;
+ char *type, *value;
+ Slapi_Mods *mods;
+ char *rawDN;
+
+ PR_ASSERT (op && ldifEntry && replGen);
+
+ memset (op, 0, sizeof (*op));
+
+ next = ldifEntry;
+ while ((line = ldif_getline(&next)) != NULL)
+ {
+ char *errmsg = NULL;
+
+ if ( *line == '\n' || *line == '\0' )
+ {
+ break;
+ }
+
+ /* this call modifies ldifEntry */
+ rc = ldif_parse_line(line, &type, &value, &vlen, &errmsg);
+ if (rc != 0)
+ {
+ if ( errmsg != NULL ) {
+ slapi_log_error(SLAPI_LOG_PARSE, repl_plugin_name_cl, "%s", errmsg);
+ slapi_ch_free( (void**)&errmsg );
+ }
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5LDIF2Operation: warning - failed to parse ldif line\n");
+ continue;
+ }
+
+ if (strcasecmp (type, T_CHANGETYPESTR) == 0)
+ {
+ op->operation_type = _cl5Str2OperationType (value);
+ }
+ else if (strcasecmp (type, T_REPLGEN) == 0)
+ {
+ *replGen = slapi_ch_strdup (value);
+ }
+ else if (strcasecmp (type, T_CSNSTR) == 0)
+ {
+ op->csn = csn_new_by_string(value);
+ }
+ else if (strcasecmp (type, T_UNIQUEIDSTR) == 0)
+ {
+ op->target_address.uniqueid = slapi_ch_strdup (value);
+ }
+ else if (strcasecmp (type, T_DNSTR) == 0)
+ {
+ PR_ASSERT (op->operation_type);
+
+ if (op->operation_type == SLAPI_OPERATION_ADD)
+ {
+ rawDN = slapi_ch_strdup (value);
+ op->target_address.dn = slapi_ch_strdup(rawDN);
+ }
+ else
+ op->target_address.dn = slapi_ch_strdup (value);
+ }
+ else if (strcasecmp (type, T_PARENTIDSTR) == 0)
+ {
+ op->p.p_add.parentuniqueid = slapi_ch_strdup (value);
+ }
+ else if (strcasecmp (type, T_NEWRDNSTR) == 0)
+ {
+ op->p.p_modrdn.modrdn_newrdn = slapi_ch_strdup (value);
+ }
+ else if (strcasecmp (type, T_DRDNFLAGSTR) == 0)
+ {
+ op->p.p_modrdn.modrdn_deloldrdn = (strcasecmp (value, "true") ? PR_FALSE : PR_TRUE);
+ }
+ else if (strcasecmp (type, T_NEWSUPERIORDNSTR) == 0)
+ {
+ op->p.p_modrdn.modrdn_newsuperior_address.dn = slapi_ch_strdup (value);
+ }
+ else if (strcasecmp (type, T_NEWSUPERIORIDSTR) == 0)
+ {
+ op->p.p_modrdn.modrdn_newsuperior_address.uniqueid = slapi_ch_strdup (value);
+ }
+ else if (strcasecmp (type, T_CHANGESTR) == 0)
+ {
+ PR_ASSERT (op->operation_type);
+
+ switch (op->operation_type)
+ {
+ case SLAPI_OPERATION_ADD: mods = parse_changes_string(value);
+ slapi_mods2entry (&(op->p.p_add.target_entry), rawDN,
+ slapi_mods_get_ldapmods_byref(mods));
+ slapi_ch_free ((void**)&rawDN);
+ slapi_mods_free (&mods);
+ break;
+
+ case SLAPI_OPERATION_MODIFY: mods = parse_changes_string(value);
+ PR_ASSERT (mods);
+ op->p.p_modify.modify_mods = slapi_mods_get_ldapmods_passout (mods);
+ slapi_mods_free (&mods);
+ break;
+
+ case SLAPI_OPERATION_MODRDN: mods = parse_changes_string(value);
+ PR_ASSERT (mods);
+ op->p.p_modrdn.modrdn_mods = slapi_mods_get_ldapmods_passout (mods);
+ slapi_mods_free (&mods);
+ break;
+
+ default: slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5LDIF2Operation: invalid operation type - %d\n",
+ op->operation_type);
+ return CL5_BAD_FORMAT;
+ }
+ }
+ }
+
+ if (IsValidOperation (op))
+ return CL5_SUCCESS;
+
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5LDIF2Operation: invalid data format\n");
+ return CL5_BAD_FORMAT;
+}
+
+static int _cl5WriteOperation(const char *replName, const char *replGen,
+ const slapi_operation_parameters *op, PRBool local)
+{
+ int rc;
+ int cnt;
+ DBT key={0};
+ DBT * data=NULL;
+ char csnStr [CSN_STRSIZE];
+ PRIntervalTime interval;
+ CL5Entry entry;
+ CL5DBFile *file = NULL;
+ Object *file_obj = NULL;
+ DB_TXN *txnid = NULL;
+
+ rc = _cl5GetDBFileByReplicaName (replName, replGen, &file_obj);
+ if (rc == CL5_NOTFOUND)
+ {
+ rc = _cl5DBOpenFileByReplicaName (replName, replGen, &file_obj,
+ PR_TRUE /* check for duplicates */);
+ if (rc != CL5_SUCCESS)
+ {
+ return rc;
+ }
+ }
+ else if (rc != CL5_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl,
+ "_cl5WriteOperation: failed to get db file for target dn (%s)",
+ op->target_address.dn);
+ return CL5_OBJSET_ERROR;
+ }
+
+ /* assign entry time - used for trimming */
+ entry.time = current_time ();
+ entry.op = (slapi_operation_parameters *)op;
+
+ /* construct the key */
+ key.data = csn_as_string(op->csn, PR_FALSE, csnStr);
+ key.size = CSN_STRSIZE;
+
+ /* construct the data */
+ data = (DBT *) slapi_ch_calloc(1, sizeof(DBT));
+ rc = _cl5Entry2DBData (&entry, (char**)&data->data, &data->size);
+ if (rc != CL5_SUCCESS)
+ {
+ char s[CSN_STRSIZE];
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5WriteOperation: failed to convert entry with csn (%s) "
+ "to db format\n", csn_as_string(op->csn,PR_FALSE,s));
+ goto done;
+ }
+
+ file = (CL5DBFile*)object_get_data (file_obj);
+ PR_ASSERT (file);
+
+ /* if this is part of ldif2cl - just write the entry without transaction */
+ if (s_cl5Desc.dbOpenMode == CL5_OPEN_LDIF2CL)
+ {
+ rc = file->db->put(file->db, NULL, &key, data, 0);
+ if (rc != 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5WriteOperation: failed to write entry; db error - %d %s\n",
+ rc, db_strerror(rc));
+ if (CL5_OS_ERR_IS_DISKFULL(rc))
+ {
+ cl5_set_diskfull();
+ }
+ rc = CL5_DB_ERROR;
+ }
+ goto done;
+ }
+
+ /* write the entry */
+ rc = EAGAIN;
+ cnt = 0;
+
+ while ((rc == EAGAIN || rc == DB_LOCK_DEADLOCK) && cnt < MAX_TRIALS)
+ {
+ if (cnt != 0)
+ {
+#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100
+ /* abort previous transaction */
+ rc = txn_abort (txnid);
+ if (rc != 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5WriteOperation: failed to abort transaction; db error - %d %s\n",
+ rc, db_strerror(rc));
+ rc = CL5_DB_ERROR;
+ goto done;
+ }
+#endif
+ /* back off */
+ interval = PR_MillisecondsToInterval(slapi_rand() % 100);
+ DS_Sleep(interval);
+ }
+#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100
+ /* begin transaction */
+ rc = txn_begin(s_cl5Desc.dbEnv, NULL /*pid*/, &txnid, 0);
+ if (rc != 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5WriteOperation: failed to start transaction; db error - %d %s\n",
+ rc, db_strerror(rc));
+ rc = CL5_DB_ERROR;
+ goto done;
+ }
+#endif
+
+ if ( file->sema )
+ {
+ PR_WaitSemaphore(file->sema);
+ }
+ rc = file->db->put(file->db, txnid, &key, data, DEFAULT_DB_OP_FLAGS);
+ if ( file->sema )
+ {
+ PR_PostSemaphore(file->sema);
+ }
+ if (CL5_OS_ERR_IS_DISKFULL(rc))
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5WriteOperation: changelog (%s) DISK FULL; db error - %d %s\n",
+ s_cl5Desc.dbDir, rc, db_strerror(rc));
+ cl5_set_diskfull();
+ rc = CL5_DB_ERROR;
+ goto done;
+ }
+ if (cnt != 0)
+ {
+ if (rc == 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "_cl5WriteOperation: retry (%d) the transaction (csn=%s) succeeded\n", cnt, (char*)key.data);
+ }
+ else if ((cnt + 1) >= MAX_TRIALS)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "_cl5WriteOperation: retry (%d) the transaction (csn=%s) failed (rc=%d)\n", cnt, (char*)key.data, rc);
+ }
+ }
+ cnt ++;
+ }
+
+ if (rc == 0) /* we successfully added entry */
+ {
+#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100
+ rc = txn_commit (txnid, 0);
+#endif
+ }
+ else
+ {
+ char s[CSN_STRSIZE];
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5WriteOperation: failed to write entry with csn (%s); "
+ "db error - %d %s\n", csn_as_string(op->csn,PR_FALSE,s),
+ rc, db_strerror(rc));
+#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100
+ rc = txn_abort (txnid);
+ if (rc != 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5WriteOperation: failed to abort transaction; db error - %d %s\n",
+ rc, db_strerror(rc));
+ }
+#endif
+ rc = CL5_DB_ERROR;
+ goto done;
+ }
+
+ /* update entry count - we assume that all entries are new */
+ PR_AtomicIncrement (&file->entryCount);
+
+ /* update purge vector if we have not seen any changes from this replica before */
+ _cl5UpdateRUV (file_obj, op->csn, PR_TRUE, PR_TRUE);
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl,
+ "cl5WriteOperation: successfully written entry with csn (%s)\n", csnStr);
+ rc = CL5_SUCCESS;
+done:
+ if (data->data)
+ slapi_ch_free ((void**)&data->data);
+ slapi_ch_free((void**) &data);
+
+ if (file_obj)
+ object_release (file_obj);
+
+ return rc;
+}
+
+static int _cl5GetFirstEntry (Object *obj, CL5Entry *entry, void **iterator, DB_TXN *txnid)
+{
+ int rc;
+ DBC *cursor = NULL;
+ DBT key={0}, data={0};
+ CL5Iterator *it;
+ CL5DBFile *file;
+
+ PR_ASSERT (obj && entry && iterator);
+
+ file = (CL5DBFile*)object_get_data (obj);
+ PR_ASSERT (file);
+ /* create cursor */
+ rc = file->db->cursor(file->db, txnid, &cursor, 0);
+ if (rc != 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5GetFirstEntry: failed to create cursor; db error - %d %s\n", rc, db_strerror(rc));
+ rc = CL5_DB_ERROR;
+ goto done;
+ }
+
+ key.flags = DB_DBT_MALLOC;
+ data.flags = DB_DBT_MALLOC;
+ while ((rc = cursor->c_get(cursor, &key, &data, DB_NEXT)) == 0)
+ {
+ /* skip service entries */
+ if (cl5HelperEntry ((char*)key.data, NULL))
+ {
+ free (key.data);
+ free (data.data);
+ continue;
+ }
+
+ /* format entry */
+ free (key.data);
+ rc = cl5DBData2Entry (data.data, data.size, entry);
+ free (data.data);
+ if (rc != 0)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5GetFirstOperation: failed to format entry\n", rc);
+ goto done;
+ }
+
+ it = (CL5Iterator*)slapi_ch_malloc (sizeof (CL5Iterator));
+ it->cursor = cursor;
+ object_acquire (obj);
+ it->file = obj;
+ *(CL5Iterator**)iterator = it;
+
+ return CL5_SUCCESS;
+ }
+
+ /* walked of the end of the file */
+ if (rc == DB_NOTFOUND)
+ {
+ rc = CL5_NOTFOUND;
+ goto done;
+ }
+
+ /* db error occured while iterating */
+ if (rc != 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5GetFirstEntry: failed to get entry; db error - %d %s\n", rc, db_strerror(rc));
+ rc = CL5_DB_ERROR;
+ goto done;
+ }
+
+ /* successfully retrieved next entry but it was out of range */
+ if (rc == CL5_SUCCESS)
+ {
+ free (key.data);
+ free (data.data);
+ rc = CL5_NOTFOUND;
+ goto done;
+ }
+
+done:;
+ /* error occured */
+ /* We didn't success in assigning this cursor to the iterator,
+ * so we need to free the cursor here */
+ if (cursor)
+ cursor->c_close(cursor);
+
+ return rc;
+}
+
+static int _cl5GetNextEntry (CL5Entry *entry, void *iterator)
+{
+ int rc;
+ CL5Iterator *it;
+ DBT key={0}, data={0};
+
+ PR_ASSERT (entry && iterator);
+
+ it = (CL5Iterator*) iterator;
+
+ key.flags = DB_DBT_MALLOC;
+ data.flags = DB_DBT_MALLOC;
+ while ((rc = it->cursor->c_get(it->cursor, &key, &data, DB_NEXT)) == 0)
+ {
+ if (cl5HelperEntry ((char*)key.data, NULL))
+ {
+ free (key.data);
+ free (data.data);
+ continue;
+ }
+
+ free (key.data);
+ /* format entry */
+ rc = cl5DBData2Entry (data.data, data.size, entry);
+ free (data.data);
+ if (rc != 0)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5GetNextEntry: failed to format entry\n", rc);
+ }
+
+ return rc;
+ }
+
+ /* walked of the end of the file or entry is out of range */
+ if (rc == 0 || rc == DB_NOTFOUND)
+ {
+ return CL5_NOTFOUND;
+ }
+
+ /* cursor operation failed */
+ if (rc != 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5GetNextEntry: failed to get entry; db error - %d %s\n", rc, db_strerror(rc));
+
+ return CL5_DB_ERROR;
+ }
+
+ return rc;
+}
+
+static int _cl5CurrentDeleteEntry (void *iterator)
+{
+ int rc;
+ CL5Iterator *it;
+ CL5DBFile *file;
+
+ PR_ASSERT (iterator);
+
+ it = (CL5Iterator*)iterator;
+
+ rc = it->cursor->c_del (it->cursor, 0);
+
+ if (rc == 0) {
+ /* decrement entry count */
+ file = (CL5DBFile*)object_get_data (it->file);
+ PR_AtomicDecrement (&file->entryCount);
+ return CL5_SUCCESS;
+ } else {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5CurrentDeleteEntry failed, err=%d %s\n",
+ rc, db_strerror(rc));
+ /* We don't free(close) the cursor here, as the caller will free it by a call to cl5DestroyIterator */
+ /* Freeing it here is a potential bug, as the cursor can't be referenced later once freed */
+ return CL5_DB_ERROR;
+ }
+}
+
+static PRBool _cl5IsValidIterator (const CL5Iterator *iterator)
+{
+ return (iterator && iterator->cursor && iterator->file);
+}
+
+static int _cl5GetOperation (Object *replica, slapi_operation_parameters *op)
+{
+ int rc;
+ DBT key={0}, data={0};
+ CL5DBFile *file;
+ CL5Entry entry;
+ Object *obj = NULL;
+ char csnStr[CSN_STRSIZE];
+
+ rc = _cl5GetDBFile (replica, &obj);
+ if (rc != CL5_SUCCESS)
+ {
+ return rc;
+ }
+
+ file = (CL5DBFile*)object_get_data (obj);
+ PR_ASSERT (file);
+
+ /* construct the key */
+ key.data = csn_as_string(op->csn, PR_FALSE, csnStr);
+ key.size = CSN_STRSIZE;
+
+ data.flags = DB_DBT_MALLOC;
+
+ rc = file->db->get(file->db, NULL/*txn*/, &key, &data, 0);
+ switch (rc)
+ {
+ case 0: entry.op = op;
+ /* Callers of this function should cl5_operation_parameters_done(op) */
+ rc = cl5DBData2Entry (data.data, data.size, &entry);
+ if (rc == CL5_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl,
+ "_cl5GetOperation: successfully retrieved operation with csn (%s)\n",
+ csnStr);
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5GetOperation: failed to convert db data to operation;"
+ " csn - %s\n", csnStr);
+ }
+ goto done;
+
+ case DB_NOTFOUND: slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5GetOperation: operation for csn (%s) is not found in db that should contain dn (%s)\n",
+ csnStr, op->target_address.dn);
+ rc = CL5_NOTFOUND;
+ goto done;
+
+ default: slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5GetOperation: failed to get entry for csn (%s); "
+ "db error - %d %s\n", csnStr, rc, db_strerror(rc));
+ rc = CL5_DB_ERROR;
+ goto done;
+ }
+
+done:;
+ if (obj)
+ object_release (obj);
+
+ if (data.data)
+ free (data.data);
+
+ return rc;
+}
+
+PRBool cl5HelperEntry (const char *csnstr, CSN *csnp)
+{
+ CSN *csn;
+ time_t csnTime;
+ PRBool retval = PR_FALSE;
+
+ if (csnp)
+ {
+ csn = csnp;
+ }
+ else
+ {
+ csn= csn_new_by_string(csnstr);
+ }
+ if (csn == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "cl5HelperEntry: failed to get csn time; csn error\n");
+ return PR_FALSE;
+ }
+ csnTime= csn_get_time(csn);
+
+ if (csnTime == ENTRY_COUNT_TIME || csnTime == PURGE_RUV_TIME)
+ {
+ retval = PR_TRUE;
+ }
+
+ if (NULL == csnp)
+ csn_free(&csn);
+ return retval;
+}
+
+/* Replay iteration helper functions */
+static PRBool _cl5ValidReplayIterator (const CL5ReplayIterator *iterator)
+{
+ if (iterator == NULL ||
+ iterator->consumerRuv == NULL || iterator->supplierRuvObj == NULL ||
+ iterator->fileObj == NULL)
+ return PR_FALSE;
+
+ return PR_TRUE;
+}
+
+/* Algorithm: ONREPL!!!
+ */
+struct replica_hash_entry
+{
+ ReplicaId rid; /* replica id */
+ PRBool sendChanges; /* indicates whether changes should be sent for this replica */
+};
+
+
+static int _cl5PositionCursorForReplay (ReplicaId consumerRID, const RUV *consumerRuv,
+ Object *replica, Object *fileObj, CL5ReplayIterator **iterator)
+{
+ CLC_Buffer *clcache = NULL;
+ CL5DBFile *file;
+ int i;
+ CSN **csns = NULL;
+ CSN *startCSN = NULL;
+ char csnStr [CSN_STRSIZE];
+ int rc = CL5_SUCCESS;
+ Object *supplierRuvObj = NULL;
+ RUV *supplierRuv = NULL;
+ ReplicaId supplierRID;
+ PRBool newReplica;
+ PRBool haveChanges = PR_FALSE;
+ char *agmt_name;
+ ReplicaId rid;
+
+ PR_ASSERT (consumerRuv && replica && fileObj && iterator);
+ csnStr[0] = '\0';
+
+ file = (CL5DBFile*)object_get_data (fileObj);
+ supplierRID = replica_get_rid((Replica*)object_get_data(replica));
+
+ /* get supplier's RUV */
+ supplierRuvObj = replica_get_ruv((Replica*)object_get_data(replica));
+ PR_ASSERT (supplierRuvObj);
+ supplierRuv = (RUV*)object_get_data (supplierRuvObj);
+ PR_ASSERT (supplierRuv);
+
+ agmt_name = get_thread_private_agmtname();
+ slapi_log_error(SLAPI_LOG_REPL, NULL, "_cl5PositionCursorForReplay (%s): Consumer RUV:\n", agmt_name);
+ ruv_dump (consumerRuv, agmt_name, NULL);
+ slapi_log_error(SLAPI_LOG_REPL, NULL, "_cl5PositionCursorForReplay (%s): Supplier RUV:\n", agmt_name);
+ ruv_dump (supplierRuv, agmt_name, NULL);
+
+ /*
+ * get the sorted list of SupplierMinCSN (if no ConsumerMaxCSN)
+ * and ConsumerMaxCSN for those RIDs where consumer is not
+ * up-to-date.
+ */
+ csns = cl5BuildCSNList (consumerRuv, supplierRuv);
+ if (csns == NULL)
+ {
+ rc = CL5_NOTFOUND;
+ goto done;
+ }
+
+ /* iterate over elements of consumer's (and/or supplier's) ruv */
+ for (i = 0; csns[i]; i++)
+ {
+ CSN *consumerMaxCSN = NULL;
+
+ rid = csn_get_replicaid(csns[i]);
+
+ /*
+ * Skip CSN that is originated from the consumer.
+ * If RID==65535, the CSN is originated from a
+ * legacy consumer. In this case the supplier
+ * and the consumer may have the same RID.
+ */
+ if (rid == consumerRID && rid != MAX_REPLICA_ID)
+ continue;
+
+ startCSN = csns[i];
+ csn_as_string(startCSN, PR_FALSE, csnStr);
+
+ rc = clcache_get_buffer ( &clcache, file->db, consumerRID, consumerRuv, supplierRuv );
+ if ( rc != 0 ) goto done;
+
+ /* This is the first loading of this iteration. For replicas
+ * already known to the consumer, we exclude the last entry
+ * sent to the consumer by using DB_NEXT. However, for
+ * replicas new to the consumer, we include the first change
+ * ever generated by that replica.
+ */
+ newReplica = ruv_get_largest_csn_for_replica (consumerRuv, rid, &consumerMaxCSN);
+ csn_free(&consumerMaxCSN);
+ rc = clcache_load_buffer (clcache, startCSN, (newReplica ? DB_SET : DB_NEXT));
+
+ /* there is a special case which can occur just after migration - in this case,
+ the consumer RUV will contain the last state of the supplier before migration,
+ but the supplier will have an empty changelog, or the supplier changelog will
+ not contain any entries within the consumer min and max CSN - also, since
+ the purge RUV contains no CSNs, the changelog has never been purged
+ ASSUMPTIONS - it is assumed that the supplier had no pending changes to send
+ to any consumers; that is, we can assume that no changes were lost due to
+ either changelog purging or database reload - bug# 603061 - richm@netscape.com
+ */
+ if (rc == 0 || (rc == DB_NOTFOUND && !ruv_has_csns(file->purgeRUV)))
+ {
+ haveChanges = PR_TRUE;
+ rc = CL5_SUCCESS;
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "%s: CSN %s found, position set for replay\n", agmt_name, csnStr);
+ break;
+ }
+ else if (rc == DB_NOTFOUND) /* entry not found */
+ {
+ /* check whether this csn should be present */
+ rc = _cl5CheckMissingCSN (startCSN, supplierRuv, file);
+ if (rc == CL5_MISSING_DATA) /* we should have had the change but we don't */
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "%s: CSN %s not found, seems to be missing\n", agmt_name, csnStr);
+ break;
+ }
+ else /* we are not as up to date or we purged */
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "%s: CSN %s not found, we aren't as up to date, or we purged\n",
+ agmt_name, csnStr);
+ continue;
+ }
+ }
+ else
+ {
+
+ /* db error */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "%s: Failed to retrieve change with CSN %s; db error - %d %s\n",
+ agmt_name, csnStr, rc, db_strerror(rc));
+ rc = CL5_DB_ERROR;
+ break;
+ }
+
+ } /* end for */
+
+ /* setup the iterator */
+ if (haveChanges)
+ {
+ *iterator = (CL5ReplayIterator*) slapi_ch_calloc (1, sizeof (CL5ReplayIterator));
+
+ if (*iterator == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "%s: _cl5PositionCursorForReplay: failed to allocate iterator\n", agmt_name);
+ rc = CL5_MEMORY_ERROR;
+ goto done;
+ }
+
+ /* ONREPL - should we make a copy of both RUVs here ?*/
+ (*iterator)->fileObj = fileObj;
+ (*iterator)->clcache = clcache; clcache = NULL;
+ (*iterator)->consumerRID = consumerRID;
+ (*iterator)->consumerRuv = consumerRuv;
+ (*iterator)->supplierRuvObj = supplierRuvObj;
+ }
+ else if (rc == CL5_SUCCESS)
+ {
+ /* we have no changes to send */
+ rc = CL5_NOTFOUND;
+ }
+
+done:
+ if ( clcache )
+ clcache_return_buffer ( &clcache );
+
+ if (csns)
+ cl5DestroyCSNList (&csns);
+
+ if (rc != CL5_SUCCESS)
+ {
+ if (supplierRuvObj)
+ object_release (supplierRuvObj);
+ }
+
+ return rc;
+}
+
+struct ruv_it
+{
+ CSN **csns; /* csn list */
+ int alloc; /* allocated size */
+ int pos; /* position in the list */
+};
+
+static int ruv_consumer_iterator (const ruv_enum_data *enum_data, void *arg)
+{
+ struct ruv_it *data = (struct ruv_it*)arg;
+
+ PR_ASSERT (data);
+
+ /* check if we have space for one more element */
+ if (data->pos >= data->alloc - 2)
+ {
+ data->alloc += 4;
+ data->csns = (CSN**) slapi_ch_realloc ((void*)data->csns, data->alloc * sizeof (CSN*));
+ }
+
+ data->csns [data->pos] = csn_dup (enum_data->csn);
+ data->pos ++;
+
+ return 0;
+}
+
+
+static int ruv_supplier_iterator (const ruv_enum_data *enum_data, void *arg)
+{
+ int i;
+ PRBool found = PR_FALSE;
+ ReplicaId rid;
+ struct ruv_it *data = (struct ruv_it*)arg;
+
+ PR_ASSERT (data);
+
+ rid = csn_get_replicaid (enum_data->min_csn);
+ /* check if the replica that generated the csn is already in the list */
+ for (i = 0; i < data->pos; i++)
+ {
+ if (rid == csn_get_replicaid (data->csns[i]))
+ {
+ found = PR_TRUE;
+
+ /* remove datacsn[i] if it is greater or equal to the supplier's maxcsn */
+ if ( csn_compare ( data->csns[i], enum_data->csn ) >= 0 )
+ {
+ int j;
+
+ csn_free ( & data->csns[i] );
+ for (j = i+1; j < data->pos; j++)
+ {
+ data->csns [j-1] = data->csns [j];
+ }
+ data->pos --;
+ }
+ break;
+ }
+ }
+
+ if (!found)
+ {
+ /* check if we have space for one more element */
+ if (data->pos >= data->alloc - 2)
+ {
+ data->alloc += 4;
+ data->csns = (CSN**)slapi_ch_realloc ((void*)data->csns,
+ data->alloc * sizeof (CSN*));
+ }
+
+ data->csns [data->pos] = csn_dup (enum_data->min_csn);
+ data->pos ++;
+ }
+ return 0;
+}
+
+
+
+static int
+my_csn_compare(const void *arg1, const void *arg2)
+{
+ return(csn_compare(*((CSN **)arg1), *((CSN **)arg2)));
+}
+
+
+
+/* builds CSN ordered list of all csns in the RUV */
+CSN** cl5BuildCSNList (const RUV *consRuv, const RUV *supRuv)
+{
+ struct ruv_it data;
+ int count, rc;
+ CSN **csns;
+
+ PR_ASSERT (consRuv);
+
+ count = ruv_replica_count (consRuv);
+ csns = (CSN**)slapi_ch_calloc (count + 1, sizeof (CSN*));
+
+ data.csns = csns;
+ data.alloc = count + 1;
+ data.pos = 0;
+
+ /* add consumer elements to the list */
+ rc = ruv_enumerate_elements (consRuv, ruv_consumer_iterator, &data);
+ if (rc == 0 && supRuv)
+ {
+ /* add supplier elements to the list */
+ rc = ruv_enumerate_elements (supRuv, ruv_supplier_iterator, &data);
+ }
+
+ /* we have no csns */
+ if (data.csns[0] == NULL)
+ {
+ /* csns might have been realloced in ruv_supplier_iterator() */
+ slapi_ch_free ((void**)&data.csns);
+ csns = NULL;
+ }
+ else
+ {
+ csns = data.csns;
+ data.csns [data.pos] = NULL;
+ if (rc == 0)
+ {
+ qsort (csns, data.pos, sizeof (CSN*), my_csn_compare);
+ }
+ else
+ {
+ cl5DestroyCSNList (&csns);
+ }
+ }
+
+ return csns;
+}
+
+void cl5DestroyCSNList (CSN*** csns)
+{
+ if (csns && *csns)
+ {
+ int i;
+
+ for (i = 0; (*csns)[i]; i++)
+ {
+ csn_free (&(*csns)[i]);
+ }
+
+ slapi_ch_free ((void**)csns);
+ }
+}
+
+/* A csn should be in the changelog if it is larger than purge vector csn for the same
+ replica and is smaller than the csn in supplier's ruv for the same replica.
+ The functions returns
+ CL5_PURGED if data was purged from the changelog or was never logged
+ because it was loaded as part of replica initialization
+ CL5_MISSING if the data erouneously missing
+ CL5_SUCCESS if that has not and should not been seen by the server
+ */
+static int _cl5CheckMissingCSN (const CSN *csn, const RUV *supplierRuv, CL5DBFile *file)
+{
+ ReplicaId rid;
+ CSN *supplierCsn = NULL;
+ CSN *purgeCsn = NULL;
+ int rc = CL5_SUCCESS;
+ char csnStr [CSN_STRSIZE];
+
+ PR_ASSERT (csn && supplierRuv && file);
+
+ rid = csn_get_replicaid (csn);
+ ruv_get_largest_csn_for_replica (supplierRuv, rid, &supplierCsn);
+ if (supplierCsn == NULL)
+ {
+ /* we have not seen any changes from this replica so it is
+ ok not to have this csn */
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5CheckMissingCSN: "
+ "can't locate %s csn: we have not seen any changes for replica %d\n",
+ csn_as_string (csn, PR_FALSE, csnStr), rid);
+ return CL5_SUCCESS;
+ }
+
+ ruv_get_largest_csn_for_replica (file->purgeRUV, rid, &purgeCsn);
+ if (purgeCsn == NULL)
+ {
+ /* changelog never contained any changes for this replica */
+ if (csn_compare (csn, supplierCsn) <= 0)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5CheckMissingCSN: "
+ "the change with %s csn was never logged because it was imported "
+ "during replica initialization\n", csn_as_string (csn, PR_FALSE, csnStr));
+ rc = CL5_PURGED_DATA; /* XXXggood is that the correct return value? */
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5CheckMissingCSN: "
+ "change with %s csn has not yet been seen by this server; "
+ " last csn seen from that replica is %s\n",
+ csn_as_string (csn, PR_FALSE, csnStr),
+ csn_as_string (supplierCsn, PR_FALSE, csnStr));
+ rc = CL5_SUCCESS;
+ }
+ }
+ else /* we have both purge and supplier csn */
+ {
+ if (csn_compare (csn, purgeCsn) < 0) /* the csn is below the purge point */
+ {
+ rc = CL5_PURGED_DATA;
+ }
+ else
+ {
+ if (csn_compare (csn, supplierCsn) <= 0) /* we should have the data but we don't */
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5CheckMissingCSN: "
+ "change with %s csn has been purged by this server; "
+ "the current purge point for that replica is %s\n",
+ csn_as_string (csn, PR_FALSE, csnStr),
+ csn_as_string (purgeCsn, PR_FALSE, csnStr));
+ rc = CL5_MISSING_DATA;
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5CheckMissingCSN: "
+ "change with %s csn has not yet been seen by this server; "
+ " last csn seen from that replica is %s\n",
+ csn_as_string (csn, PR_FALSE, csnStr),
+ csn_as_string (supplierCsn, PR_FALSE, csnStr));
+ rc = CL5_SUCCESS;
+ }
+ }
+ }
+
+ if (supplierCsn)
+ csn_free (&supplierCsn);
+
+ if (purgeCsn)
+ csn_free (&purgeCsn);
+
+ return rc;
+}
+
+/* Helper functions that work with individual changelog files */
+
+/* file name format : <replica name>_<replica generation>db{2,3} */
+static PRBool _cl5FileName2Replica (const char *file_name, Object **replica)
+{
+ Replica *r;
+ char *repl_name, *file_gen, *repl_gen;
+ int len;
+
+ PR_ASSERT (file_name && replica);
+
+ *replica = NULL;
+
+ /* this is database file */
+ if (_cl5FileEndsWith (file_name, DB_EXTENSION) ||
+ _cl5FileEndsWith (file_name, DB_EXTENSION_DB3) )
+ {
+ repl_name = slapi_ch_strdup (file_name);
+ file_gen = strstr(repl_name, FILE_SEP);
+ if (file_gen)
+ {
+ int extlen = strlen(DB_EXTENSION);
+ *file_gen = '\0';
+ file_gen += strlen (FILE_SEP);
+ len = strlen (file_gen);
+ if (len <= extlen + 1)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5FileName2Replica "
+ "invalid file name (%s)\n", file_name);
+ }
+ else
+ {
+ /* get rid of the file extension */
+ file_gen [len - extlen - 1] = '\0';
+ *replica = replica_get_by_name (repl_name);
+ if (*replica)
+ {
+ /* check that generation matches the one in replica object */
+ r = (Replica*)object_get_data (*replica);
+ repl_gen = replica_get_generation (r);
+ PR_ASSERT (repl_gen);
+ if (strcmp (file_gen, repl_gen) != 0)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5FileName2Replica "
+ "replica generation mismatch for replica at (%s), "
+ "file generation %s, new replica generation %s\n",
+ slapi_sdn_get_dn (replica_get_root (r)), file_gen, repl_gen);
+
+ object_release (*replica);
+ *replica = NULL;
+ }
+ slapi_ch_free ((void**)&repl_gen);
+ }
+ }
+ slapi_ch_free ((void**)&repl_name);
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5FileName2Replica "
+ "malformed file name - %s\n", file_name);
+ }
+
+ return PR_TRUE;
+ }
+ else
+ return PR_FALSE;
+}
+
+/* file name format : <replica name>_<replica generation>db{2,3} */
+static char* _cl5Replica2FileName (Object *replica)
+{
+ const char *replName;
+ char *replGen, *fileName;
+ Replica *r;
+
+ PR_ASSERT (replica);
+
+ r = (Replica*)object_get_data (replica);
+ PR_ASSERT (r);
+
+ replName = replica_get_name (r);
+ replGen = replica_get_generation (r);
+
+ fileName = _cl5MakeFileName (replName, replGen) ;
+
+ slapi_ch_free ((void**)&replGen);
+
+ return fileName;
+}
+
+static char* _cl5MakeFileName (const char *replName, const char *replGen)
+{
+ char *fileName;
+ fileName = slapi_ch_malloc (strlen (replName) + strlen (replGen) +
+ strlen (DB_EXTENSION) + 3/* '_' + '.' + '\0' */);
+ sprintf (fileName, "%s%s%s.%s", replName, FILE_SEP, replGen, DB_EXTENSION);
+
+ return fileName;
+}
+
+/* open file that corresponds to a particular database */
+static int _cl5DBOpenFile (Object *replica, Object **obj, PRBool checkDups)
+{
+ int rc;
+ const char *replName;
+ char *replGen;
+ Replica *r;
+
+ PR_ASSERT (replica);
+
+ r = (Replica*)object_get_data (replica);
+ replName = replica_get_name (r);
+ PR_ASSERT (replName);
+ replGen = replica_get_generation (r);
+ PR_ASSERT (replGen);
+
+ rc = _cl5DBOpenFileByReplicaName (replName, replGen, obj, checkDups);
+
+ slapi_ch_free ((void**)&replGen);
+
+ return rc;
+}
+
+static int _cl5DBOpenFileByReplicaName (const char *replName, const char *replGen,
+ Object **obj, PRBool checkDups)
+{
+ int rc = CL5_SUCCESS;
+ Object *tmpObj;
+ CL5DBFile *file;
+ char *file_name;
+
+ PR_ASSERT (replName && replGen);
+
+ if (checkDups)
+ {
+ PR_Lock (s_cl5Desc.fileLock);
+ file_name = _cl5MakeFileName (replName, replGen);
+ tmpObj = objset_find (s_cl5Desc.dbFiles, _cl5CompareDBFile, file_name);
+ slapi_ch_free((void **)&file_name);
+ file_name = NULL;
+ if (tmpObj) /* this file already exist */
+ {
+ /* if we were asked for file handle - keep the handle */
+ if (obj)
+ {
+ *obj = tmpObj;
+ }
+ else
+ {
+ object_release (tmpObj);
+ }
+
+ rc = CL5_SUCCESS;
+ goto done;
+ }
+ }
+
+ rc = _cl5NewDBFile (replName, replGen, &file);
+ if (rc == CL5_SUCCESS)
+ {
+ /* This creates the file but doesn't set the init flag
+ * The flag is set later when the purge and max ruvs are set.
+ * This is to prevent some thread to get file access before the
+ * structure is fully initialized */
+ rc = _cl5AddDBFile (file, &tmpObj);
+ if (rc == CL5_SUCCESS)
+ {
+ /* read purge RUV - done here because it needs file object rather than file pointer */
+ rc = _cl5ReadRUV (replGen, tmpObj, PR_TRUE);
+ if (rc != CL5_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5DBOpenFileByReplicaName: failed to get purge RUV\n");
+ goto done;
+ }
+
+ /* read ruv that represents the upper bound of the changes stored in the file */
+ rc = _cl5ReadRUV (replGen, tmpObj, PR_FALSE);
+ if (rc != CL5_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5DBOpenFileByReplicaName: failed to get upper bound RUV\n");
+ goto done;
+ }
+
+ /* Mark the DB File initialize */
+ _cl5DBFileInitialized(tmpObj);
+
+ if (obj)
+ {
+ *obj = tmpObj;
+ }
+ else
+ {
+ object_release (tmpObj);
+ }
+ }
+ }
+
+done:;
+ if (rc != CL5_SUCCESS)
+ {
+ if (file)
+ _cl5DBCloseFile ((void**)&file);
+ }
+
+ if (checkDups)
+ {
+ PR_Unlock (s_cl5Desc.fileLock);
+ }
+
+ return rc;
+}
+
+/* adds file to the db file list */
+static int _cl5AddDBFile (CL5DBFile *file, Object **obj)
+{
+ int rc;
+ Object *tmpObj;
+
+ PR_ASSERT (file);
+
+ tmpObj = object_new (file, _cl5DBCloseFile);
+ rc = objset_add_obj(s_cl5Desc.dbFiles, tmpObj);
+ if (rc != OBJSET_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5AddDBFile: failed to add db file to the list; "
+ "repl_objset error - %d\n", rc);
+ object_release (tmpObj);
+ return CL5_OBJSET_ERROR;
+ }
+
+ if (obj)
+ {
+ *obj = tmpObj;
+ }
+ else
+ object_release (tmpObj);
+
+ return CL5_SUCCESS;
+}
+
+static int _cl5NewDBFile (const char *replName, const char *replGen, CL5DBFile** dbFile)
+{
+ int rc;
+ DB *db = NULL;
+ char *name;
+ char *semadir;
+#ifdef HPUX
+ char cwd [PATH_MAX+1];
+#endif
+
+ PR_ASSERT (replName && replGen && dbFile);
+
+ (*dbFile) = (CL5DBFile *)slapi_ch_calloc (1, sizeof (CL5DBFile));
+ if (*dbFile == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5NewDBFile: memory allocation failed\n");
+ return CL5_MEMORY_ERROR;
+ }
+
+ name = _cl5MakeFileName (replName, replGen);
+ {
+ /* The subname argument allows applications to have
+ * subdatabases, i.e., multiple databases inside of a single
+ * physical file. This is useful when the logical databases
+ * are both numerous and reasonably small, in order to
+ * avoid creating a large number of underlying files.
+ */
+ char *subname = NULL;
+ DB_ENV *dbEnv = s_cl5Desc.dbEnv;
+
+ rc = db_create(&db, dbEnv, 0);
+ if (0 != rc) {
+ goto out;
+ }
+
+ rc = db->set_pagesize(
+ db,
+ s_cl5Desc.dbConfig.pageSize);
+
+ if (0 != rc) {
+ goto out;
+ }
+
+#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 3300
+ rc = db->set_malloc(db, malloc);
+ if (0 != rc) {
+ goto out;
+ }
+#endif
+
+ DB_OPEN(s_cl5Desc.dbEnvOpenFlags,
+ db, NULL /* txnid */, name, subname, DB_BTREE,
+ DB_CREATE | DB_THREAD, s_cl5Desc.dbConfig.fileMode, rc);
+ }
+out:
+ if (rc != 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5NewDBFile: db_open failed; db error - %d %s\n",
+ rc, db_strerror(rc));
+ rc = CL5_DB_ERROR;
+ goto done;
+ }
+
+ (*dbFile)->db = db;
+ (*dbFile)->name = name;
+ (*dbFile)->replName = slapi_ch_strdup (replName);
+ (*dbFile)->replGen = slapi_ch_strdup (replGen);
+
+ /*
+ * Considerations for setting up cl semaphore:
+ * (1) The NT version of SleepyCat uses test-and-set mutexes
+ * at the DB page level instead of blocking mutexes. That has
+ * proven to be a killer for the changelog DB, as this DB is
+ * accessed by multiple a reader threads (the repl thread) and
+ * writer threads (the server ops threads) usually at the last
+ * pages of the DB, due to the sequential nature of the changelog
+ * keys. To avoid the test-and-set mutexes, we could use semaphore
+ * to serialize the writers and avoid the high mutex contention
+ * that SleepyCat is unable to avoid.
+ * (2) [610948] Linux master hangs for 2 hours
+ * [611239] _cl5DeadlockMain: lock_detect succeeded
+ * (3) DS 6.2 introduced the semaphore on all platforms (replaced
+ * the serial lock used on Windows and Linux described above).
+ * The number of the concurrent writes now is configurable by
+ * nsslapd-changelogmaxconcurrentwrites (the server needs to
+ * be restarted).
+ */
+
+ semadir = s_cl5Desc.dbDir;
+#ifdef HPUX
+ /*
+ * HP sem_open() does not allow pathname component "./" or "../"
+ * in the semaphore name. For simplicity and to avoid doing
+ * chdir() in multi-thread environment, current working dir
+ * (log dir) is used to replace the original semaphore dir
+ * if it contains "./".
+ */
+ if ( strstr ( semadir, "./" ) != NULL && getcwd ( cwd, PATH_MAX+1 ) != NULL )
+ {
+ semadir = cwd;
+ }
+#endif
+
+ if ( semadir != NULL )
+ {
+ (*dbFile)->semaName = slapi_ch_malloc (strlen(semadir) + strlen(replName) + strlen(".sema") + 10);
+ sprintf ((*dbFile)->semaName, "%s/%s.sema", semadir, replName);
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5NewDBFile: semaphore %s\n", (*dbFile)->semaName);
+ (*dbFile)->sema = PR_OpenSemaphore((*dbFile)->semaName, PR_SEM_CREATE, 0666, s_cl5Desc.dbConfig.maxConcurrentWrites );
+ slapi_log_error (SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5NewDBFile: maxConcurrentWrites=%d\n", s_cl5Desc.dbConfig.maxConcurrentWrites );
+ }
+
+ if ((*dbFile)->sema == NULL )
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5NewDBFile: failed to create semaphore %s; NSPR error - %d\n",
+ (*dbFile)->semaName ? (*dbFile)->semaName : "(nil)", PR_GetError ());
+ rc = CL5_SYSTEM_ERROR;
+ goto done;
+ }
+
+ /* compute number of entries in the file */
+ /* ONREPL - to improve performance, we keep entry count in memory
+ and write it down during shutdown. Problem: this will not
+ work with multiple processes. Do we have to worry about that?
+ */
+ if (s_cl5Desc.dbOpenMode == CL5_OPEN_NORMAL)
+ {
+ rc = _cl5GetEntryCount (*dbFile);
+ if (rc != CL5_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "_cl5NewDBFile: failed to get entry count\n");
+ goto done;
+ }
+ }
+
+done:
+ if (rc != CL5_SUCCESS)
+ {
+ if (dbFile)
+ _cl5DBCloseFile ((void**)dbFile);
+ /* slapi_ch_free accepts NULL pointer */
+ slapi_ch_free ((void**)&name);
+
+ slapi_ch_free ((void**)dbFile);
+ }
+
+ return rc;
+}
+
+static void _cl5DBCloseFile (void **data)
+{
+ CL5DBFile *file;
+ char fullpathname[MAXPATHLEN];
+
+ PR_ASSERT (data);
+
+ file = *(CL5DBFile**)data;
+
+ /* close the file */
+ /* if this is normal close or close after import, update entry count */
+ if ((s_cl5Desc.dbOpenMode == CL5_OPEN_NORMAL && s_cl5Desc.dbState == CL5_STATE_CLOSING) ||
+ s_cl5Desc.dbOpenMode == CL5_OPEN_LDIF2CL)
+ {
+ _cl5WriteEntryCount (file);
+ _cl5WriteRUV (file, PR_TRUE);
+ _cl5WriteRUV (file, PR_FALSE);
+ }
+
+ /* close file */
+ if (file->db)
+ file->db->close(file->db, 0);
+
+ if (file->flags & DB_FILE_DELETED)
+ {
+ PR_snprintf(fullpathname, MAXPATHLEN, "%s/%s", s_cl5Desc.dbDir, file->name);
+ if (PR_Delete(fullpathname) != PR_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5DBCloseFile: "
+ "failed to remove (%s) file; NSPR error - %d\n", file->name, PR_GetError ());
+
+ }
+ }
+
+ /* slapi_ch_free accepts NULL pointer */
+ slapi_ch_free ((void**)&file->name);
+ slapi_ch_free ((void**)&file->replName);
+ slapi_ch_free ((void**)&file->replGen);
+ if (file->sema) {
+ PR_CloseSemaphore (file->sema);
+ PR_DeleteSemaphore (file->semaName);
+ file->sema = NULL;
+ }
+ slapi_ch_free ((void**)&file->semaName);
+
+ slapi_ch_free (data);
+}
+
+static int _cl5GetDBFile (Object *replica, Object **obj)
+{
+ char *fileName;
+
+ PR_ASSERT (replica && obj);
+
+ fileName = _cl5Replica2FileName (replica);
+
+ *obj = objset_find(s_cl5Desc.dbFiles, _cl5CompareDBFile, fileName);
+ slapi_ch_free ((void**)&fileName);
+ if (*obj)
+ {
+ return CL5_SUCCESS;
+ }
+ else
+ {
+ return CL5_NOTFOUND;
+ }
+}
+
+static int _cl5GetDBFileByReplicaName (const char *replName, const char *replGen,
+ Object **obj)
+{
+ char *fileName;
+
+ PR_ASSERT (replName && replGen && obj);
+
+ fileName = _cl5MakeFileName (replName, replGen);
+
+ *obj = objset_find(s_cl5Desc.dbFiles, _cl5CompareDBFile, fileName);
+ slapi_ch_free ((void**)&fileName);
+ if (*obj)
+ {
+ return CL5_SUCCESS;
+ }
+ else
+ {
+ return CL5_NOTFOUND;
+ }
+}
+
+static void _cl5DBDeleteFile (Object *obj)
+{
+ CL5DBFile *file;
+
+ PR_ASSERT (obj);
+
+ file = (CL5DBFile*)object_get_data (obj);
+ PR_ASSERT (file);
+ file->flags |= DB_FILE_DELETED;
+ objset_remove_obj(s_cl5Desc.dbFiles, obj);
+ object_release (obj);
+}
+
+static void _cl5DBFileInitialized (Object *obj)
+{
+ CL5DBFile *file;
+
+ PR_ASSERT (obj);
+
+ file = (CL5DBFile*)object_get_data (obj);
+ PR_ASSERT (file);
+ file->flags |= DB_FILE_INIT;
+}
+
+static int _cl5CompareDBFile (Object *el1, const void *el2)
+{
+ CL5DBFile *file;
+ const char *name;
+
+ PR_ASSERT (el1 && el2);
+
+ file = (CL5DBFile*) object_get_data (el1);
+ name = (const char*) el2;
+ return ((file->flags & DB_FILE_INIT) ? strcmp (file->name, name) : 1);
+}
+
+static int _cl5CopyDBFiles (const char *srcDir, const char *destDir, Object **replicas)
+{
+ char srcFile [MAXPATHLEN + 1];
+ char destFile[MAXPATHLEN + 1];
+ int rc;
+ Object *obj;
+ CL5DBFile *file;
+
+ /* ONREPL currently, dbidlist is ignored because db code can't handle discrepancy between
+ transaction log and present files; this should change before 5.0 ships */
+ obj = objset_first_obj (s_cl5Desc.dbFiles);
+ while (obj)
+ {
+ file = (CL5DBFile*)object_get_data (obj);
+ PR_ASSERT (file);
+
+ PR_snprintf(srcFile, MAXPATHLEN, "%s/%s", srcDir, file->name);
+ PR_snprintf(destFile, MAXPATHLEN, "%s/%s", destDir, file->name);
+ rc = copyfile(srcFile, destFile, 0, FILE_CREATE_MODE);
+ if (rc != 0)
+ {
+ object_release (obj);
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5CopyDBFiles: failed to copy %s from %s to %s\n",
+ file, srcDir, destDir);
+ return CL5_SYSTEM_ERROR;
+ }
+
+ obj = objset_next_obj (s_cl5Desc.dbFiles, obj);
+ }
+
+ return CL5_SUCCESS;
+}
+
+/*
+ * return 1: true (the "filename" ends with "ext")
+ * return 0: false
+ */
+static int _cl5FileEndsWith(const char *filename, const char *ext)
+{
+ char *p = NULL;
+ int flen = strlen(filename);
+ int elen = strlen(ext);
+ if (0 == flen || 0 == elen)
+ {
+ return 0;
+ }
+ p = strstr(filename, ext);
+ if (NULL == p)
+ {
+ return 0;
+ }
+ if (p - filename + elen == flen)
+ {
+ return 1;
+ }
+ return 0;
+}
+
+static int _cl5ExportFile (PRFileDesc *prFile, Object *obj)
+{
+ int rc;
+ void *iterator = NULL;
+ slapi_operation_parameters op = {0};
+ char *buff;
+ PRInt32 len, wlen;
+ CL5Entry entry;
+ CL5DBFile *file;
+
+ PR_ASSERT (prFile && obj);
+
+ file = (CL5DBFile*)object_get_data (obj);
+ PR_ASSERT (file);
+
+ ruv_dump (file->purgeRUV, "clpurgeruv", prFile);
+ ruv_dump (file->maxRUV, "clmaxruv", prFile);
+ slapi_write_buffer (prFile, "\n", strlen("\n"));
+
+ entry.op = &op;
+ rc = _cl5GetFirstEntry (obj, &entry, &iterator, NULL);
+ while (rc == CL5_SUCCESS)
+ {
+ rc = _cl5Operation2LDIF (&op, file->replGen, &buff, &len);
+ if (rc != CL5_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5ExportLDIF: failed to convert operation to ldif\n");
+ operation_parameters_done (&op);
+ break;
+ }
+
+ wlen = slapi_write_buffer (prFile, buff, len);
+ slapi_ch_free((void **)&buff);
+ if (wlen < len)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5ExportLDIF: failed to write to ldif file\n");
+ rc = CL5_SYSTEM_ERROR;
+ operation_parameters_done (&op);
+ break;
+ }
+
+ cl5_operation_parameters_done (&op);
+
+ rc = _cl5GetNextEntry (&entry, iterator);
+ }
+
+ cl5_operation_parameters_done (&op);
+
+ if (iterator)
+ cl5DestroyIterator (iterator);
+
+ if (rc != CL5_NOTFOUND)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "_cl5ExportLDIF: failed to retrieve changelog entry\n");
+ }
+ else
+ {
+ rc = CL5_SUCCESS;
+ }
+
+ return rc;
+}
+
+static PRBool _cl5ReplicaInList (Object *replica, Object **replicas)
+{
+ int i;
+
+ PR_ASSERT (replica && replicas);
+
+ /* ONREPL I think it should be sufficient to just compare replica pointers */
+ for (i=0; replicas[i]; i++)
+ {
+ if (replica == replicas[i])
+ return PR_TRUE;
+ }
+
+ return PR_FALSE;
+}
+
+static char* _cl5GetHelperEntryKey (int type, char *csnStr)
+{
+ CSN *csn= csn_new();
+ char *rt;
+
+ csn_set_time(csn, type);
+ csn_set_replicaid(csn, 0);
+
+ rt = csn_as_string(csn, PR_FALSE, csnStr);
+ csn_free(&csn);
+
+ return rt;
+}
+
+static Object* _cl5GetReplica (const slapi_operation_parameters *op, const char* replGen)
+{
+ Slapi_DN *sdn;
+ Object *replObj;
+ Replica *replica;
+ char *newGen;
+
+ PR_ASSERT (op && replGen);
+
+ sdn = slapi_sdn_new_dn_byref(op->target_address.dn);
+
+ replObj = replica_get_replica_from_dn (sdn);
+ if (replObj)
+ {
+ /* check to see if replica generation has not change */
+ replica = (Replica*)object_get_data (replObj);
+ PR_ASSERT (replica);
+ newGen = replica_get_generation (replica);
+ PR_ASSERT (newGen);
+ if (strcmp (replGen, newGen) != 0)
+ {
+ object_release (replObj);
+ replObj = NULL;
+ }
+
+ slapi_ch_free ((void**)&replGen);
+ }
+
+ slapi_sdn_free (&sdn);
+
+ return replObj;
+}
+
+int
+cl5_is_diskfull()
+{
+ int rc;
+ PR_Lock(cl5_diskfull_lock);
+ rc = cl5_diskfull_flag;
+ PR_Unlock(cl5_diskfull_lock);
+ return rc;
+}
+
+static void
+cl5_set_diskfull()
+{
+ PR_Lock(cl5_diskfull_lock);
+ cl5_diskfull_flag = 1;
+ PR_Unlock(cl5_diskfull_lock);
+}
+
+static void
+cl5_set_no_diskfull()
+{
+ PR_Lock(cl5_diskfull_lock);
+ cl5_diskfull_flag = 0;
+ PR_Unlock(cl5_diskfull_lock);
+}
+
+int
+cl5_diskspace_is_available()
+{
+ int rval = 1;
+
+#if defined( OS_solaris ) || defined( hpux )
+ struct statvfs fsbuf;
+ if (statvfs(s_cl5Desc.dbDir, &fsbuf) < 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5_diskspace_is_available: Cannot get file system info\n");
+ rval = 0;
+ }
+ else
+ {
+ unsigned long fsiz = fsbuf.f_bavail * fsbuf.f_frsize;
+ if (fsiz < NO_DISK_SPACE)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5_diskspace_is_available: No enough diskspace for changelog: (%u bytes free)\n", fsiz);
+ rval = 0;
+ }
+ else if (fsiz > MIN_DISK_SPACE)
+ {
+ /* assume recovered */
+ cl5_set_no_diskfull();
+ }
+ }
+#endif
+#if defined( linux )
+ struct statfs fsbuf;
+ if (statfs(s_cl5Desc.dbDir, &fsbuf) < 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5_diskspace_is_available: Cannot get file system info\n");
+ rval = 0;
+ }
+ else
+ {
+ unsigned long fsiz = fsbuf.f_bavail * fsbuf.f_bsize;
+ if (fsiz < NO_DISK_SPACE)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "cl5_diskspace_is_available: No enough diskspace for changelog: (%u bytes free)\n", fsiz);
+ rval = 0;
+ }
+ else if (fsiz > MIN_DISK_SPACE)
+ {
+ /* assume recovered */
+ cl5_set_no_diskfull();
+ }
+ }
+#endif
+ return rval;
+}
diff --git a/ldap/servers/plugins/replication/cl5_api.h b/ldap/servers/plugins/replication/cl5_api.h
new file mode 100644
index 00000000..49296df2
--- /dev/null
+++ b/ldap/servers/plugins/replication/cl5_api.h
@@ -0,0 +1,478 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* cl5_api.h - interface to 5.0 changelog */
+
+#ifndef CL5_API_H
+#define CL5_API_H
+
+#include "repl5.h"
+#include "repl5_prot_private.h"
+
+#define CL5_TYPE "Changelog5" /* changelog type */
+#define VERSION_SIZE 127 /* size of the buffer to hold changelog version */
+#define CL5_DEFAULT_CONFIG -1 /* value that indicates to changelog to use default */
+#define CL5_STR_IGNORE "-1" /* tels function to ignore this parameter */
+#define CL5_NUM_IGNORE -1 /* tels function to ignore this parameter */
+#define CL5_STR_UNLIMITED "0" /* represent unlimited value (trimming ) */
+#define CL5_NUM_UNLIMITED 0 /* represent unlimited value (trimming ) */
+
+#define CL5_OS_ERR_IS_DISKFULL(err) ((err)==ENOSPC || (err)==EFBIG)
+
+/***** Data Structures *****/
+
+/* changelog configuration structure */
+typedef struct cl5dbconfig
+{
+ size_t cacheSize; /* cache size in bytes */
+ PRBool durableTrans; /* flag that tells not to sync log when trans commits */
+ PRInt32 checkpointInterval; /* checkpoint interval in seconds */
+ PRBool circularLogging; /* flag to archive and trancate log */
+ size_t pageSize; /* page size in bytes */
+ size_t logfileSize; /* maximum log size in bytes */
+ size_t maxTxnSize; /* maximum txn table size in count*/
+ PRInt32 fileMode; /* file mode */
+ PRBool verbose; /* Get libdb to exhale debugging info */
+ PRBool debug; /* Will libdb emit debugging info into our log ? */
+ PRInt32 tricklePercentage; /* guaranteed percentage of clean cache pages; 0 - 100 */
+ PRInt32 spinCount; /* DB Mutex spin count */
+ PRUint32 nb_lock_config; /* Number of locks in the DB lock table. New in 5.1 */
+/* The next 2 parameters are needed for configuring the changelog cache. New in 5.1 */
+ PRUint32 maxChCacheEntries;
+ PRUint32 maxChCacheSize;
+ PRUint32 maxConcurrentWrites; /* 6.2 max number of concurrent cl writes */
+} CL5DBConfig;
+
+/* changelog entry format */
+typedef struct cl5entry
+{
+ slapi_operation_parameters *op; /* operation applied to the server */
+ time_t time; /* time added to the cl; used for trimming */
+} CL5Entry;
+
+/* default values for the changelog configuration structure above */
+/*
+ * For historical reasons, dbcachesize refers to number of bytes at the DB level,
+ * whereas cachesize refers to number of entries at the changelog cache level (cachememsize is the
+ * one refering to number of bytes at the changelog cache level)
+ */
+#define CL5_DEFAULT_CONFIG_DB_DBCACHESIZE 10485760 /* 10M bytes */
+#define CL5_DEFAULT_CONFIG_DB_DURABLE_TRANSACTIONS 1
+#define CL5_DEFAULT_CONFIG_DB_CHECKPOINT_INTERVAL 60
+#define CL5_DEFAULT_CONFIG_DB_CIRCULAR_LOGGING 1
+#define CL5_DEFAULT_CONFIG_DB_PAGE_SIZE 8*1024
+#define CL5_DEFAULT_CONFIG_DB_LOGFILE_SIZE 0
+#define CL5_DEFAULT_CONFIG_DB_VERBOSE 0
+#define CL5_DEFAULT_CONFIG_DB_DEBUG 0
+#define CL5_DEFAULT_CONFIG_DB_TRICKLE_PERCENTAGE 40
+#define CL5_DEFAULT_CONFIG_DB_SPINCOUNT 0
+#define CL5_DEFAULT_CONFIG_DB_TXN_MAX 200
+#define CL5_DEFAULT_CONFIG_CACHESIZE 3000 /* number of entries */
+#define CL5_DEFAULT_CONFIG_CACHEMEMSIZE 1048576 /* 1 M bytes */
+#define CL5_DEFAULT_CONFIG_NB_LOCK 1000 /* Number of locks in the lock table of the DB */
+
+/*
+ * Small number of concurrent writes degradate the throughput.
+ * Large one increases deadlock.
+ */
+#ifdef SOLARIS
+#define CL5_DEFAULT_CONFIG_MAX_CONCURRENT_WRITES 10
+#else
+#define CL5_DEFAULT_CONFIG_MAX_CONCURRENT_WRITES 2
+#endif
+
+
+#define CL5_MIN_DB_DBCACHESIZE 524288 /* min 500K bytes */
+#define CL5_MIN_CACHESIZE 500 /* min number of entries */
+#define CL5_MIN_CACHEMEMSIZE 262144 /* min 250K bytes */
+#define CL5_MIN_NB_LOCK 1000 /* The minimal number of locks in the DB (Same as default) */
+
+/* data structure that allows iteration through changelog */
+typedef struct cl5replayiterator CL5ReplayIterator;
+
+/* changelog state */
+typedef enum
+{
+ CL5_STATE_NONE, /* changelog has not been initialized */
+ CL5_STATE_CLOSING, /* changelog is about to close; all threads must exit */
+ CL5_STATE_CLOSED, /* changelog has been initialized, but not opened, or open and then closed */
+ CL5_STATE_OPEN /* changelog is opened */
+} CL5State;
+
+/* error codes */
+enum
+{
+ CL5_SUCCESS, /* successful operation */
+ CL5_BAD_DATA, /* invalid parameter passed to the function */
+ CL5_BAD_FORMAT, /* db data has unexpected format */
+ CL5_BAD_STATE, /* changelog is in an incorrect state for attempted operation */
+ CL5_BAD_DBVERSION, /* changelog has invalid dbversion */
+ CL5_DB_ERROR, /* database error */
+ CL5_NOTFOUND, /* requested entry or value was not found */
+ CL5_MEMORY_ERROR, /* memory allocation failed */
+ CL5_SYSTEM_ERROR, /* NSPR error occured, use PR_Error for furhter info */
+ CL5_CSN_ERROR, /* CSN API failed */
+ CL5_RUV_ERROR, /* RUV API failed */
+ CL5_OBJSET_ERROR, /* namedobjset api failed */
+ CL5_PURGED_DATA, /* requested data has been purged */
+ CL5_MISSING_DATA, /* data should be in the changelog, but is missing */
+ CL5_UNKNOWN_ERROR /* unclassified error */
+};
+
+/***** Module APIs *****/
+
+/* Name: cl5Init
+ Description: initializes changelog module; must be called by a single thread
+ before any function of the module.
+ Parameters: none
+ Return: CL5_SUCCESS if function is successful;
+ CL5_BAD_DATA if invalid directory is passed;
+ CL5_SYSTEM error if NSPR call fails.
+ */
+int cl5Init ();
+
+/* Name: cl5Cleanup
+ Description: performs cleanup of the changelog module. Must be called by a single
+ thread. It will closed db if it is still open.
+ Parameters: none
+ Return: none
+ */
+void cl5Cleanup ();
+
+/* Name: cl5Open
+ Description: opens changelog ; must be called after changelog is
+ initialized using cl5Init. It is thread safe and the second
+ call is ignored.
+ Parameters: dir - changelog dir
+ config - db configuration parameters; currently not used
+ openMode - open mode
+ Return: CL5_SUCCESS if successfull;
+ CL5_BAD_DATA if invalid directory is passed;
+ CL5_BAD_DBVERSION if dbversion file is missing or has unexpected data
+ CL5_SYSTEM_ERROR if NSPR error occured (during db directory creation);
+ CL5_MEMORY_ERROR if memory allocation fails;
+ CL5_DB_ERROR if db initialization or open fails.
+ */
+int cl5Open (const char *dir, const CL5DBConfig *config);
+
+/* Name: cl5Close
+ Description: closes changelog and cleanups changelog module; waits until
+ all threads are done using changelog
+ Parameters: none
+ Return: CL5_SUCCESS if successful;
+ CL5_BAD_STATE if db is not in the open state;
+ CL5_SYSTEM_ERROR if NSPR call fails
+ */
+int cl5Close ();
+
+/* Name: cl5Delete
+ Description: removes changelog
+ Parameters: dir - changelog directory
+ Return: CL5_SUCCESS if successful;
+ CL5_BAD_STATE if the changelog is not in closed state;
+ CL5_BAD_DATA if invalid directory supplied
+ CL5_SYSTEM_ERROR if NSPR call fails
+ */
+int cl5Delete (const char *dir);
+
+/* Name: cl5OpenDB
+ Description: opens changelog file for specified file
+ Parameters: replica - replica whose file we wish to open
+ Return: CL5_SUCCESS if successful;
+ CL5_BAD_STATE if the changelog is not initialized;
+ CL5_BAD_DATA - if NULL id is supplied
+ */
+int cl5OpenDB (Object *replica);
+
+/* Name: cl5CloseDB
+ Description: closes changelog file for the specified replica
+ Parameters: replica - replica whose file we wish to close
+ Return: CL5_SUCCESS if successful;
+ CL5_BAD_STATE if the changelog is not initialized;
+ CL5_BAD_DATA - if NULL id is supplied
+ CL5_NOTFOUND - nothing is known about specified database
+ */
+int cl5CloseDB (Object *replica);
+
+/* Name: cl5DeleteDB
+ Description: asynchronously removes changelog file for the specified replica.
+ The file is physically removed when it is no longer in use.
+ This function is called when a backend is removed or reloaded.
+ Parameters: replica - replica whose file we wish to delete
+ Return: CL5_SUCCESS if successful;
+ CL5_BAD_STATE if the changelog is not initialized;
+ CL5_BAD_DATA - if NULL id is supplied
+ CL5_NOTFOUND - nothing is known about specified database
+ */
+int cl5DeleteDB (Object *replica);
+
+/* Name: cl5DeleteDBSync
+ Description: The same as cl5DeleteDB except the function does not return
+ until the file is removed.
+*/
+int cl5DeleteDBSync (Object *replica);
+
+/* Name: cl5GetUpperBoundRUV
+ Description: retrieves vector that represent the upper bound of changes
+ stored in the changelog for the replica.
+ Parameters: r - replica for which the vector is requested
+ ruv - contains a copy of the upper bound ruv if function is successful;
+ unchanged otherwise. It is responsobility pf the caller to free
+ the ruv when it is no longer is in use
+ Return: CL5_SUCCESS if function is successfull
+ CL5_BAD_STATE if the changelog is not initialized;
+ CL5_BAD_DATA - if NULL id is supplied
+ CL5_NOTFOUND, if changelog file for replica is not found
+ */
+int cl5GetUpperBoundRUV (Replica *r, RUV **ruv);
+
+/* Name: cl5Backup
+ Description: makes a backup of the changelog including *.db2,
+ log files, and dbversion. Can be called with the changelog in either open or
+ closed state.
+ Parameters: bkDir - directory to which the data is backed up;
+ created if it does not exist
+ replicas - optional list of replicas whose changes should be backed up;
+ if the list is NULL, entire changelog is backed up.
+ Return: CL5_SUCCESS if function is successful;
+ CL5_BAD_DATA if invalid directory is passed;
+ CL5_BAD_STATE if changelog has not been initialized;
+ CL5_DB_ERROR if db call fails;
+ CL5_SYSTEM_ERROR if NSPR call or file copy failes.
+ */
+int cl5Backup (const char *bkDir, Object **replicas);
+
+/* Name: cl5Restore
+ Description: restores changelog from the backed up copy. Changelog must be ibnitalized and closed.
+ Parameters: clDir - changelog dir
+ bkDir - directory that contains the backup
+ replicas - optional list of replicas whose changes should be recovered;
+ if the list is NULL, entire changelog is recovered.
+ Return: CL5_SUCCESS if function is successfull;
+ CL5_BAD_DATA if invalid parameter is passed;
+ CL5_BAD_STATE if changelog is open or not initialized;
+ CL5_DB_ERROR if db call fails;
+ CL5_SYSTEM_ERROR if NSPR call of file copy fails
+ */
+int cl5Restore (const char *clDir, const char *bkDir, Object **replicas);
+
+/* Name: cl5ExportLDIF
+ Description: dumps changelog to an LDIF file; changelog can be open or closed.
+ Parameters: clDir - changelog dir
+ ldifFile - full path to ldif file to write
+ replicas - optional list of replicas whose changes should be exported;
+ if the list is NULL, entire changelog is exported.
+ Return: CL5_SUCCESS if function is successfull;
+ CL5_BAD_DATA if invalid parameter is passed;
+ CL5_BAD_STATE if changelog is not initialized;
+ CL5_DB_ERROR if db api fails;
+ CL5_SYSTEM_ERROR if NSPR call fails;
+ CL5_MEMORY_ERROR if memory allocation fials.
+ */
+int cl5ExportLDIF (const char *ldifFile, Object **replicas);
+
+/* Name: cl5ImportLDIF
+ Description: imports ldif file into changelog; changelog must be in the closed state
+ Parameters: clDir - changelog dir
+ ldifFile - absolute path to the ldif file to import
+ replicas - optional list of replicas whose data should be imported;
+ if the list is NULL, all data in the file is imported.
+ Return: CL5_SUCCESS if function is successfull;
+ CL5_BAD_DATA if invalid parameter is passed;
+ CL5_BAD_STATE if changelog is open or not inititalized;
+ CL5_DB_ERROR if db api fails;
+ CL5_SYSTEM_ERROR if NSPR call fails;
+ CL5_MEMORY_ERROR if memory allocation fials.
+ */
+int cl5ImportLDIF (const char *clDir, const char *ldifFile, Object **replicas);
+
+/* Name: cl5GetState
+ Description: returns database state
+ Parameters: none
+ Return: changelog state
+ */
+
+int cl5GetState ();
+
+/* Name: cl5ConfigTrimming
+ Description: sets changelog trimming parameters
+ Parameters: maxEntries - maximum number of entries in the log;
+ maxAge - maximum entry age;
+ Return: CL5_SUCCESS if successful;
+ CL5_BAD_STATE if changelog has not been open
+ */
+int cl5ConfigTrimming (int maxEntries, const char *maxAge);
+
+/* Name: cl5GetOperation
+ Description: retireves operation specified by its csn and databaseid
+ Parameters: op - must contain csn and databaseid; the rest of data is
+ filled if function is successfull
+ Return: CL5_SUCCESS if function is successfull;
+ CL5_BAD_DATA if invalid op is passed;
+ CL5_BAD_STATE if db has not been initialized;
+ CL5_NOTFOUND if entry was not found;
+ CL5_DB_ERROR if any other db error occured;
+ CL5_BADFORMAT if db data format does not match entry format.
+ */
+int cl5GetOperation (Object *replica, slapi_operation_parameters *op);
+
+/* Name: cl5GetFirstOperation
+ Description: retrieves first operation for a particular database
+ replica - replica for which the operation should be retrieved.
+ Parameters: op - buffer to store the operation;
+ iterator - to be passed to the call to cl5GetNextOperation
+ Return: CL5_SUCCESS, if successful
+ CL5_BADDATA, if operation is NULL
+ CL5_BAD_STATE, if changelog is not open
+ CL5_DB_ERROR, if db call fails
+ */
+int cl5GetFirstOperation (Object *replica, slapi_operation_parameters *op, void **iterator);
+
+/* Name: cl5GetNextOperation
+ Description: retrieves the next op from the changelog as defined by the iterator
+ Parameters: replica - replica for which the operation should be retrieved.
+ op - returned operation, if function is successful
+ iterator - in: identifies op to retrieve; out: identifies next op
+ Return: CL5_SUCCESS, if successful
+ CL5_BADDATA, if invalid parameter is supplied
+ CL5_BAD_STATE, if changelog is not open
+ CL5_NOTFOUND, empty changelog
+ CL5_DB_ERROR, if db call fails
+ */
+int cl5GetNextOperation (slapi_operation_parameters *op, void *iterator);
+
+/* Name: cl5DestroyIterator
+ Description: destroys iterator once iteration through changelog is done
+ Parameters: iterator - iterator to destroy
+ Return: CL5_SUCCESS, if successful
+ CL5_BADDATA, if invalid parameters is supplied
+ CL5_BAD_STATE, if changelog is not open
+ CL5_DB_ERROR, if db call fails
+ */
+void cl5DestroyIterator (void *iterator);
+
+/* Name: cl5WriteOperation
+ Description: writes operation to changelog
+ Parameters: repl_name - name of the replica to which operation applies
+ repl_gen - replica generation for the operation
+ !!!Note that we pass name and generation rather than
+ replica object since generation can change while operation
+ is in progress (if the data is reloaded). !!!
+ op - operation to write
+ local - this is a non-replicated operation
+ Return: CL5_SUCCESS if function is successfull;
+ CL5_BAD_DATA if invalid op is passed;
+ CL5_BAD_STATE if db has not been initialized;
+ CL5_MEMORY_ERROR if memory allocation failed;
+ CL5_DB_ERROR if any other db error occured;
+ */
+int cl5WriteOperation(const char *repl_name, const char *repl_gen,
+ const slapi_operation_parameters *op, PRBool local);
+
+/* Name: cl5CreateReplayIterator
+ Description: creates an iterator that allows to retireve changes that should
+ to be sent to the consumer identified by ruv The iteration is peformed by
+ repeated calls to cl5GetNextOperationToReplay.
+ Parameters: replica - replica whose data we wish to iterate;
+ ruv - consumer ruv;
+ iterator - iterator to be passed to cl5GetNextOperationToReplay call
+ Return: CL5_SUCCESS, if function is successfull;
+ CL5_MISSING_DATA, if data that should be in the changelog is missing
+ CL5_PURGED_DATA, if some data that consumer needs has been purged.
+ Note that the iterator can be non null if the supplier contains
+ some data that needs to be sent to the consumer
+ CL5_NOTFOUND if the consumer is up to data with respect to the supplier
+ CL5_BAD_DATA if invalid parameter is passed;
+ CL5_BAD_STATE if db has not been open;
+ CL5_DB_ERROR if any other db error occured;
+ CL5_MEMORY_ERROR if memory allocation fails.
+ */
+int cl5CreateReplayIterator (Private_Repl_Protocol *prp, const RUV *ruv,
+ CL5ReplayIterator **iterator);
+
+/* Name: cl5GetNextOperationToReplay
+ Description: retrieves next operation to be sent to the consumer and
+ that was created on a particular master. Consumer and master info
+ is encoded in the iterator parameter that must be created by calling
+ to cl5CreateIterator.
+ Parameters: iterator - iterator that identifies next entry to retrieve;
+ op - operation retireved if function is successful
+ Return: CL5_SUCCESS if function is successfull;
+ CL5_BAD_DATA if invalid parameter is passed;
+ CL5_NOTFOUND if end of iteration list is reached
+ CL5_DB_ERROR if any other db error occured;
+ CL5_BADFORMAT if data in db is of unrecognized format;
+ CL5_MEMORY_ERROR if memory allocation fails.
+ */
+int cl5GetNextOperationToReplay (CL5ReplayIterator *iterator,
+ CL5Entry *entry);
+
+/* Name: cl5DestroyReplayIterator
+ Description: destorys iterator
+ Parameters: iterator - iterator to destory
+ Return: none
+ */
+void cl5DestroyReplayIterator (CL5ReplayIterator **iterator);
+
+/* Name: cl5DeleteOnClose
+ Description: marks changelog for deletion when it is closed
+ Parameters: flag; if flag = 1 then delete else don't
+ Return: none
+ */
+
+void cl5DeleteOnClose (PRBool rm);
+
+/* Name: cl5GetDir
+ Description: returns changelog directory; must be freed by the caller;
+ Parameters: none
+ Return: copy of the directory; caller needs to free the string
+ */
+
+char *cl5GetDir ();
+
+/* Name: cl5Exist
+ Description: checks if a changelog exists in the specified directory
+ Parameters: clDir - directory to check;
+ Return: 1 - if changelog exists; 0 - otherwise
+ */
+
+PRBool cl5Exist (const char *clDir);
+
+/* Name: cl5GetOperationCount
+ Description: returns number of entries in the changelog. The changelog must be
+ open for the value to be meaningful.
+ Parameters: replica - optional parameter that specifies the replica whose operations
+ we wish to count; if NULL all changelog entries are counted
+ Return: number of entries in the changelog
+ */
+
+int cl5GetOperationCount (Object *replica);
+
+/* Name: cl5_operation_parameters_done
+ Description: frees all parameters that are not freed by operation_parameters_done
+ function in the server.
+
+ */
+
+void cl5_operation_parameters_done (struct slapi_operation_parameters *sop);
+
+/* Name: cl5CreateDirIfNeeded
+ Description: Create the directory if it doesn't exist yet
+ Parameters: dir - Contains the name of the directory to create. Must not be NULL
+ Return: CL5_SUCCESS if succeeded or existed,
+ CL5_SYSTEM_ERROR if failed.
+*/
+
+int cl5CreateDirIfNeeded (const char *dir);
+int cl5DBData2Entry (const char *data, PRUint32 len, CL5Entry *entry);
+
+PRBool cl5HelperEntry (const char *csnstr, CSN *csn);
+CSN** cl5BuildCSNList (const RUV *consRuv, const RUV *supRuv);
+void cl5DestroyCSNList (CSN*** csns);
+
+int cl5_is_diskfull();
+int cl5_diskspace_is_available();
+
+#endif
diff --git a/ldap/servers/plugins/replication/cl5_clcache.c b/ldap/servers/plugins/replication/cl5_clcache.c
new file mode 100644
index 00000000..585a7266
--- /dev/null
+++ b/ldap/servers/plugins/replication/cl5_clcache.c
@@ -0,0 +1,910 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2003 Netscape Communications Corporation
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#include "errno.h" /* ENOMEM, EVAL used by Berkeley DB */
+#include "db.h" /* Berkeley DB */
+#include "cl5.h" /* changelog5Config */
+#include "cl5_clcache.h"
+
+/*
+ * Constants for the buffer pool:
+ *
+ * DEFAULT_CLC_BUFFER_PAGE_COUNT
+ * Little performance boost if it is too small.
+ *
+ * DEFAULT_CLC_BUFFER_PAGE_SIZE
+ * Its value is determined based on the DB requirement that
+ * the buffer size should be the multiple of 1024.
+ */
+#define DEFAULT_CLC_BUFFER_COUNT_MIN 10
+#define DEFAULT_CLC_BUFFER_COUNT_MAX 0
+#define DEFAULT_CLC_BUFFER_PAGE_COUNT 32
+#define DEFAULT_CLC_BUFFER_PAGE_SIZE 1024
+
+static enum {
+ CLC_STATE_READY = 0, /* ready to iterate */
+ CLC_STATE_UP_TO_DATE, /* remote RUV already covers the CSN */
+ CLC_STATE_CSN_GT_RUV, /* local RUV doesn't conver the CSN */
+ CLC_STATE_NEW_RID, /* unknown RID to local RUVs */
+ CLC_STATE_UNSAFE_RUV_CHANGE,/* (RUV1 < maxcsn-in-buffer) && (RUV1 < RUV1') */
+ CLC_STATE_DONE, /* no more change */
+ CLC_STATE_ABORTING /* abort replication session */
+};
+
+typedef struct clc_busy_list CLC_Busy_List;
+
+struct csn_seq_ctrl_block {
+ ReplicaId rid; /* RID this block serves */
+ CSN *consumer_maxcsn; /* Don't send CSN <= this */
+ CSN *local_maxcsn; /* Don't send CSN > this */
+ CSN *prev_local_maxcsn; /* */
+ int state; /* CLC_STATE_* */
+};
+
+/*
+ * Each cl5replayiterator acquires a buffer from the buffer pool
+ * at the beginning of a replication session, and returns it back
+ * at the end.
+ */
+struct clc_buffer {
+ char *buf_agmt_name; /* agreement acquired this buffer */
+ ReplicaId buf_consumer_rid; /* help checking threshold csn */
+ const RUV *buf_consumer_ruv; /* used to skip change */
+ const RUV *buf_local_ruv; /* used to refresh local_maxcsn */
+
+ /*
+ * fields for retriving data from DB
+ */
+ int buf_state;
+ CSN *buf_current_csn;
+ int buf_load_flag; /* db flag DB_MULTIPLE_KEY, DB_SET, DB_NEXT */
+ DBC *buf_cursor;
+ DBT buf_key; /* current csn string */
+ DBT buf_data; /* data retrived from db */
+ void *buf_record_ptr; /* ptr to the current record in data */
+ CSN *buf_missing_csn; /* used to detect persistent missing of CSN */
+
+ /* fields for control the CSN sequence sent to the consumer */
+ struct csn_seq_ctrl_block *buf_cscbs [MAX_NUM_OF_MASTERS];
+ int buf_num_cscbs; /* number of csn sequence ctrl blocks */
+
+ /* fields for debugging stat */
+ int buf_load_cnt; /* number of loads for session */
+ int buf_record_cnt; /* number of changes for session */
+ int buf_record_skipped; /* number of changes skipped */
+
+ /*
+ * fields that should be accessed via bl_lock or pl_lock
+ */
+ CLC_Buffer *buf_next; /* next buffer in the same list */
+ CLC_Busy_List *buf_busy_list; /* which busy list I'm in */
+};
+
+/*
+ * Each changelog has a busy buffer list
+ */
+struct clc_busy_list {
+ PRLock *bl_lock;
+ DB *bl_db; /* changelog db handle */
+ CLC_Buffer *bl_buffers; /* busy buffers of this list */
+ CLC_Busy_List *bl_next; /* next busy list in the pool */
+};
+
+/*
+ * Each process has a buffer pool
+ */
+struct clc_pool {
+ PRRWLock *pl_lock; /* cl writer and agreements */
+ DB_ENV **pl_dbenv; /* pointer to DB_ENV for all the changelog files */
+ CLC_Busy_List *pl_busy_lists; /* busy buffer lists, one list per changelog file */
+ int pl_buffer_cnt_now; /* total number of buffers */
+ int pl_buffer_cnt_min; /* free a newly returned buffer if _now > _min */
+ int pl_buffer_cnt_max; /* no use */
+ int pl_buffer_default_pages; /* num of pages in a new buffer */
+};
+
+/* static variables */
+static struct clc_pool *_pool = NULL; /* process's buffer pool */
+
+/* static prototypes */
+static int clcache_adjust_anchorcsn ( CLC_Buffer *buf );
+static void clcache_refresh_consumer_maxcsns ( CLC_Buffer *buf );
+static int clcache_refresh_local_maxcsns ( CLC_Buffer *buf );
+static int clcache_skip_change ( CLC_Buffer *buf );
+static int clcache_load_buffer_bulk ( CLC_Buffer *buf, int flag );
+static int clcache_open_cursor ( DB_TXN *txn, CLC_Buffer *buf, DBC **cursor );
+static int clcache_cursor_get ( DBC *cursor, CLC_Buffer *buf, int flag );
+static struct csn_seq_ctrl_block *clcache_new_cscb ();
+static void clcache_free_cscb ( struct csn_seq_ctrl_block ** cscb );
+static CLC_Buffer *clcache_new_buffer ( ReplicaId consumer_rid );
+static void clcache_delete_buffer ( CLC_Buffer **buf );
+static CLC_Busy_List *clcache_new_busy_list ();
+static void clcache_delete_busy_list ( CLC_Busy_List **bl );
+static int clcache_enqueue_busy_list( DB *db, CLC_Buffer *buf );
+static void csn_dup_or_init_by_csn ( CSN **csn1, CSN *csn2 );
+
+/*
+ * Initiates the process buffer pool. This should be done
+ * once and only once when process starts.
+ */
+int
+clcache_init ( DB_ENV **dbenv )
+{
+ _pool = (struct clc_pool*) slapi_ch_calloc ( 1, sizeof ( struct clc_pool ));
+ _pool->pl_dbenv = dbenv;
+ _pool->pl_buffer_cnt_min = DEFAULT_CLC_BUFFER_COUNT_MIN;
+ _pool->pl_buffer_cnt_max = DEFAULT_CLC_BUFFER_COUNT_MAX;
+ _pool->pl_buffer_default_pages = DEFAULT_CLC_BUFFER_COUNT_MAX;
+ _pool->pl_lock = PR_NewRWLock (PR_RWLOCK_RANK_NONE, "clcache_pl_lock");
+ return 0;
+}
+
+/*
+ * This is part of a callback function when changelog configuration
+ * is read or updated.
+ */
+void
+clcache_set_config ( CL5DBConfig *config )
+{
+ if ( config == NULL ) return;
+
+ PR_RWLock_Wlock ( _pool->pl_lock );
+
+ _pool->pl_buffer_cnt_max = config->maxChCacheEntries;
+
+ /*
+ * According to http://www.sleepycat.com/docs/api_c/dbc_get.html,
+ * data buffer should be a multiple of 1024 bytes in size
+ * for DB_MULTIPLE_KEY operation.
+ */
+ _pool->pl_buffer_default_pages = config->maxChCacheSize / DEFAULT_CLC_BUFFER_PAGE_SIZE + 1;
+ _pool->pl_buffer_default_pages = DEFAULT_CLC_BUFFER_PAGE_COUNT;
+ if ( _pool->pl_buffer_default_pages <= 0 ) {
+ _pool->pl_buffer_default_pages = DEFAULT_CLC_BUFFER_PAGE_COUNT;
+ }
+
+ PR_RWLock_Unlock ( _pool->pl_lock );
+}
+
+/*
+ * Gets the pointer to a thread dedicated buffer, or allocates
+ * a new buffer if there is no buffer allocated yet for this thread.
+ *
+ * This is called when a cl5replayiterator is created for
+ * a replication session.
+ */
+int
+clcache_get_buffer ( CLC_Buffer **buf, DB *db, ReplicaId consumer_rid, const RUV *consumer_ruv, const RUV *local_ruv )
+{
+ int rc = 0;
+
+ if ( buf == NULL ) return CL5_BAD_DATA;
+
+ *buf = NULL;
+
+ if ( NULL != ( *buf = (CLC_Buffer*) get_thread_private_cache()) ) {
+ (*buf)->buf_state = CLC_STATE_READY;
+ (*buf)->buf_load_cnt = 0;
+ (*buf)->buf_record_cnt = 0;
+ (*buf)->buf_record_skipped = 0;
+ (*buf)->buf_cursor = NULL;
+ (*buf)->buf_num_cscbs = 0;
+ }
+ else {
+ *buf = clcache_new_buffer ( consumer_rid );
+ if ( *buf ) {
+ if ( 0 == clcache_enqueue_busy_list ( db, *buf ) ) {
+ set_thread_private_cache ( (void*) (*buf) );
+ }
+ else {
+ clcache_delete_buffer ( buf );
+ }
+ }
+ }
+
+ if ( NULL != *buf ) {
+ (*buf)->buf_consumer_ruv = consumer_ruv;
+ (*buf)->buf_local_ruv = local_ruv;
+ }
+ else {
+ slapi_log_error ( SLAPI_LOG_FATAL, get_thread_private_agmtname(),
+ "clcache_get_buffer: can't allocate new buffer\n" );
+ rc = ENOMEM;
+ }
+
+ return rc;
+}
+
+/*
+ * Returns a buffer back to the buffer pool.
+ */
+void
+clcache_return_buffer ( CLC_Buffer **buf )
+{
+ int i;
+
+ slapi_log_error ( SLAPI_LOG_REPL, (*buf)->buf_agmt_name,
+ "session end: state=%d load=%d sent=%d skipped=%d\n",
+ (*buf)->buf_state,
+ (*buf)->buf_load_cnt,
+ (*buf)->buf_record_cnt - (*buf)->buf_record_skipped,
+ (*buf)->buf_record_skipped );
+
+ for ( i = 0; i < (*buf)->buf_num_cscbs; i++ ) {
+ clcache_free_cscb ( &(*buf)->buf_cscbs[i] );
+ }
+ (*buf)->buf_num_cscbs = 0;
+
+ if ( (*buf)->buf_cursor ) {
+
+ (*buf)->buf_cursor->c_close ( (*buf)->buf_cursor );
+ (*buf)->buf_cursor = NULL;
+ }
+}
+
+/*
+ * Loads a buffer from DB.
+ *
+ * anchorcsn - passed in for the first load of a replication session;
+ * flag - DB_SET to load in the key CSN record.
+ * DB_NEXT to load in the records greater than key CSN.
+ * return - DB error code instead of cl5 one because of the
+ * historic reason.
+ */
+int
+clcache_load_buffer ( CLC_Buffer *buf, CSN *anchorcsn, int flag )
+{
+ int rc = 0;
+
+ clcache_refresh_local_maxcsns ( buf );
+
+ /* Set the loading key */
+ if ( anchorcsn ) {
+ clcache_refresh_consumer_maxcsns ( buf );
+ buf->buf_load_flag = DB_MULTIPLE_KEY;
+ csn_as_string ( anchorcsn, 0, (char*)buf->buf_key.data );
+ slapi_log_error ( SLAPI_LOG_REPL, buf->buf_agmt_name,
+ "session start: anchorcsn=%s\n", (char*)buf->buf_key.data );
+ }
+ else if ( csn_get_time(buf->buf_current_csn) == 0 ) {
+ /* time == 0 means this csn has never been set */
+ rc = DB_NOTFOUND;
+ }
+ else if ( clcache_adjust_anchorcsn ( buf ) != 0 ) {
+ rc = DB_NOTFOUND;
+ }
+ else {
+ csn_as_string ( buf->buf_current_csn, 0, (char*)buf->buf_key.data );
+ slapi_log_error ( SLAPI_LOG_REPL, buf->buf_agmt_name,
+ "load next: anchorcsn=%s\n", (char*)buf->buf_key.data );
+ }
+
+ if ( rc == 0 ) {
+
+ buf->buf_state = CLC_STATE_READY;
+ rc = clcache_load_buffer_bulk ( buf, flag );
+
+ /* Reset some flag variables */
+ if ( rc == 0 ) {
+ int i;
+ for ( i = 0; i < buf->buf_num_cscbs; i++ ) {
+ buf->buf_cscbs[i]->state = CLC_STATE_READY;
+ }
+ }
+ else if ( anchorcsn ) {
+ /* Report error only when the missing is persistent */
+ if ( buf->buf_missing_csn && csn_compare (buf->buf_missing_csn, anchorcsn) == 0 ) {
+ slapi_log_error ( SLAPI_LOG_FATAL, buf->buf_agmt_name,
+ "Can't locate CSN %s in the changelog (DB rc=%d). The consumer may need to be reinitialized.\n",
+ (char*)buf->buf_key.data, rc );
+ }
+ else {
+ csn_dup_or_init_by_csn (&buf->buf_missing_csn, anchorcsn);
+ }
+ }
+ }
+ if ( rc != 0 ) {
+ slapi_log_error ( SLAPI_LOG_REPL, buf->buf_agmt_name,
+ "clcache_load_buffer: rc=%d\n", rc );
+ }
+
+ return rc;
+}
+
+static int
+clcache_load_buffer_bulk ( CLC_Buffer *buf, int flag )
+{
+ DB_TXN *txn = NULL;
+ DBC *cursor = NULL;
+ int rc;
+
+ /* txn control seems not improving anything so turn it off */
+ /*
+ if ( *(_pool->pl_dbenv) ) {
+ txn_begin( *(_pool->pl_dbenv), NULL, &txn, 0 );
+ }
+ */
+
+ PR_Lock ( buf->buf_busy_list->bl_lock );
+ if ( 0 == ( rc = clcache_open_cursor ( txn, buf, &cursor )) ) {
+
+ if ( flag == DB_NEXT ) {
+ /* For bulk read, position the cursor before read the next block */
+ rc = cursor->c_get ( cursor,
+ & buf->buf_key,
+ & buf->buf_data,
+ DB_SET );
+ }
+
+ /*
+ * Continue if the error is no-mem since we don't need to
+ * load in the key record anyway with DB_SET.
+ */
+ if ( 0 == rc || ENOMEM == rc )
+ rc = clcache_cursor_get ( cursor, buf, flag );
+
+ }
+
+ /*
+ * Don't keep a cursor open across the whole replication session.
+ * That had caused noticable DB resource contention.
+ */
+ if ( cursor ) {
+ cursor->c_close ( cursor );
+ }
+
+ if ( txn ) {
+ txn->commit ( txn, DB_TXN_NOSYNC );
+ }
+
+ PR_Unlock ( buf->buf_busy_list->bl_lock );
+
+ buf->buf_record_ptr = NULL;
+ if ( 0 == rc ) {
+ DB_MULTIPLE_INIT ( buf->buf_record_ptr, &buf->buf_data );
+ if ( NULL == buf->buf_record_ptr )
+ rc = DB_NOTFOUND;
+ else
+ buf->buf_load_cnt++;
+ }
+
+ return rc;
+}
+
+/*
+ * Gets the next change from the buffer.
+ * *key : output - key of the next change, or NULL if no more change
+ * *data: output - data of the next change, or NULL if no more change
+ */
+int
+clcache_get_next_change ( CLC_Buffer *buf, void **key, size_t *keylen, void **data, size_t *datalen, CSN **csn )
+{
+ int skip = 1;
+ int rc = 0;
+
+ do {
+ *key = *data = NULL;
+ *keylen = *datalen = 0;
+
+ if ( buf->buf_record_ptr ) {
+ DB_MULTIPLE_KEY_NEXT ( buf->buf_record_ptr, &buf->buf_data,
+ *key, *keylen, *data, *datalen );
+ }
+
+ /*
+ * We're done with the current buffer. Now load the next chunk.
+ */
+ if ( NULL == *key && CLC_STATE_READY == buf->buf_state ) {
+ rc = clcache_load_buffer ( buf, NULL, DB_NEXT );
+ if ( 0 == rc && buf->buf_record_ptr ) {
+ DB_MULTIPLE_KEY_NEXT ( buf->buf_record_ptr, &buf->buf_data,
+ *key, *keylen, *data, *datalen );
+ }
+ }
+
+ /* Compare the new change to the local and remote RUVs */
+ if ( NULL != *key ) {
+ buf->buf_record_cnt++;
+ csn_init_by_string ( buf->buf_current_csn, (char*)*key );
+ skip = clcache_skip_change ( buf );
+ if (skip) buf->buf_record_skipped++;
+ }
+ }
+ while ( rc == 0 && *key && skip );
+
+ if ( NULL == *key ) {
+ *key = NULL;
+ *csn = NULL;
+ rc = DB_NOTFOUND;
+ }
+ else {
+ *csn = buf->buf_current_csn;
+ slapi_log_error ( SLAPI_LOG_REPL, buf->buf_agmt_name,
+ "load=%d rec=%d csn=%s\n",
+ buf->buf_load_cnt, buf->buf_record_cnt, (char*)*key );
+ }
+
+ return rc;
+}
+
+static void
+clcache_refresh_consumer_maxcsns ( CLC_Buffer *buf )
+{
+ int i;
+
+ for ( i = 0; i < buf->buf_num_cscbs; i++ ) {
+ ruv_get_largest_csn_for_replica (
+ buf->buf_consumer_ruv,
+ buf->buf_cscbs[i]->rid,
+ &buf->buf_cscbs[i]->consumer_maxcsn );
+ }
+}
+
+static int
+clcache_refresh_local_maxcsn ( const ruv_enum_data *rid_data, void *data )
+{
+ CLC_Buffer *buf = (CLC_Buffer*) data;
+ ReplicaId rid;
+ int rc = 0;
+ int i;
+
+ rid = csn_get_replicaid ( rid_data->csn );
+
+ /*
+ * No need to create cscb for consumer's RID.
+ * If RID==65535, the CSN is originated from a
+ * legacy consumer. In this case the supplier
+ * and the consumer may have the same RID.
+ */
+ if ( rid == buf->buf_consumer_rid && rid != MAX_REPLICA_ID )
+ return rc;
+
+ for ( i = 0; i < buf->buf_num_cscbs; i++ ) {
+ if ( buf->buf_cscbs[i]->rid == rid )
+ break;
+ }
+ if ( i >= buf->buf_num_cscbs ) {
+ buf->buf_cscbs[i] = clcache_new_cscb ();
+ if ( buf->buf_cscbs[i] == NULL ) {
+ return -1;
+ }
+ buf->buf_cscbs[i]->rid = rid;
+ buf->buf_num_cscbs++;
+ }
+
+ csn_dup_or_init_by_csn ( &buf->buf_cscbs[i]->local_maxcsn, rid_data->csn );
+
+ if ( buf->buf_cscbs[i]->consumer_maxcsn &&
+ csn_compare (buf->buf_cscbs[i]->consumer_maxcsn, rid_data->csn) >= 0 ) {
+ /* No change need to be sent for this RID */
+ buf->buf_cscbs[i]->state = CLC_STATE_UP_TO_DATE;
+ }
+
+ return rc;
+}
+
+static int
+clcache_refresh_local_maxcsns ( CLC_Buffer *buf )
+{
+ int i;
+
+ for ( i = 0; i < buf->buf_num_cscbs; i++ ) {
+ csn_dup_or_init_by_csn ( &buf->buf_cscbs[i]->prev_local_maxcsn,
+ buf->buf_cscbs[i]->local_maxcsn );
+ }
+ return ruv_enumerate_elements ( buf->buf_local_ruv, clcache_refresh_local_maxcsn, buf );
+}
+
+/*
+ * Algorithm:
+ *
+ * 1. Snapshot local RUVs;
+ * 2. Load buffer;
+ * 3. Send to the consumer only those CSNs that are covered
+ * by the RUVs snapshot taken in the first step;
+ * All CSNs that are covered by the RUVs snapshot taken in the
+ * first step are guaranteed in consecutive order for the respected
+ * RIDs because of the the CSN pending list control;
+ * A CSN that is not covered by the RUVs snapshot may be out of order
+ * since it is possible that a smaller CSN might not have committed
+ * yet by the time the buffer was loaded.
+ * 4. Determine anchorcsn for each RID:
+ *
+ * Case| Local vs. Buffer | New Local | Next
+ * | MaxCSN MaxCSN | MaxCSN | Anchor-CSN
+ * ----+-------------------+-----------+----------------
+ * 1 | Cl >= Cb | * | Cb
+ * 2 | Cl < Cb | Cl | Cb
+ * 3 | Cl < Cb | Cl2 | Cl
+ *
+ * 5. Determine anchorcsn for next load:
+ * Anchor-CSN = min { all Next-Anchor-CSN, Buffer-MaxCSN }
+ */
+static int
+clcache_adjust_anchorcsn ( CLC_Buffer *buf )
+{
+ PRBool hasChange = PR_FALSE;
+ struct csn_seq_ctrl_block *cscb;
+ int rc = 0;
+ int i;
+
+ if ( buf->buf_state == CLC_STATE_READY ) {
+ for ( i = 0; i < buf->buf_num_cscbs; i++ ) {
+ cscb = buf->buf_cscbs[i];
+
+ if ( cscb->state == CLC_STATE_UP_TO_DATE )
+ continue;
+
+ /*
+ * Case 3 unsafe ruv change: next buffer load should start
+ * from where the maxcsn in the old ruv was. Since each
+ * cscb has remembered the maxcsn sent to the consumer,
+ * CSNs that may be loaded again could easily be skipped.
+ */
+ if ( cscb->prev_local_maxcsn &&
+ csn_compare (cscb->prev_local_maxcsn, buf->buf_current_csn) < 0 &&
+ csn_compare (cscb->local_maxcsn, cscb->prev_local_maxcsn) != 0 ) {
+ hasChange = PR_TRUE;
+ cscb->state = CLC_STATE_READY;
+ csn_init_by_csn ( buf->buf_current_csn, cscb->prev_local_maxcsn );
+ csn_as_string ( cscb->prev_local_maxcsn, 0, (char*)buf->buf_key.data );
+ slapi_log_error ( SLAPI_LOG_REPL, buf->buf_agmt_name,
+ "adjust anchor csn upon %s\n",
+ ( cscb->state == CLC_STATE_CSN_GT_RUV ? "out of sequence csn" : "unsafe ruv change") );
+ continue;
+ }
+
+ /*
+ * check if there are still changes to send for this RID
+ * Assume we had compared the local maxcsn and the consumer
+ * max csn before this function was called and hence the
+ * cscb->state had been set accordingly.
+ */
+ if ( hasChange == PR_FALSE &&
+ csn_compare (cscb->local_maxcsn, buf->buf_current_csn) > 0 ) {
+ hasChange = PR_TRUE;
+ }
+ }
+ }
+
+ if ( !hasChange ) {
+ buf->buf_state = CLC_STATE_DONE;
+ }
+
+ return buf->buf_state;
+}
+
+static int
+clcache_skip_change ( CLC_Buffer *buf )
+{
+ struct csn_seq_ctrl_block *cscb = NULL;
+ ReplicaId rid;
+ int skip = 1;
+ int i;
+
+ do {
+
+ rid = csn_get_replicaid ( buf->buf_current_csn );
+
+ /*
+ * Skip CSN that is originated from the consumer.
+ * If RID==65535, the CSN is originated from a
+ * legacy consumer. In this case the supplier
+ * and the consumer may have the same RID.
+ */
+ if (rid == buf->buf_consumer_rid && rid != MAX_REPLICA_ID)
+ break;
+
+ /* Skip helper entry (ENTRY_COUNT, PURGE_RUV and so on) */
+ if ( cl5HelperEntry ( NULL, buf->buf_current_csn ) == PR_TRUE ) {
+ slapi_log_error ( SLAPI_LOG_REPL, buf->buf_agmt_name,
+ "Skip helper entry type=%d\n", csn_get_time( buf->buf_current_csn ));
+ break;
+ }
+
+ /* Find csn sequence control block for the current rid */
+ for (i = 0; i < buf->buf_num_cscbs && buf->buf_cscbs[i]->rid != rid; i++);
+
+ /* Skip CSN whose RID is unknown to the local RUV snapshot */
+ if ( i >= buf->buf_num_cscbs ) {
+ buf->buf_state = CLC_STATE_NEW_RID;
+ break;
+ }
+
+ cscb = buf->buf_cscbs[i];
+
+ /* Skip if the consumer is already up-to-date for the RID */
+ if ( cscb->state == CLC_STATE_UP_TO_DATE ) {
+ break;
+ }
+
+ /* Skip CSN whose preceedents are not covered by local RUV snapshot */
+ if ( cscb->state == CLC_STATE_CSN_GT_RUV ) {
+ break;
+ }
+
+ /* Skip CSNs already covered by consumer RUV */
+ if ( cscb->consumer_maxcsn &&
+ csn_compare ( buf->buf_current_csn, cscb->consumer_maxcsn ) <= 0 ) {
+ break;
+ }
+
+ /* Send CSNs that are covered by the local RUV snapshot */
+ if ( csn_compare ( buf->buf_current_csn, cscb->local_maxcsn ) <= 0 ) {
+ skip = 0;
+ csn_dup_or_init_by_csn ( &cscb->consumer_maxcsn, buf->buf_current_csn );
+ break;
+ }
+
+ /*
+ * Promote the local maxcsn to its next neighbor
+ * to keep the current session going. Skip if we
+ * are not sure if current_csn is the neighbor.
+ */
+ if ( csn_time_difference(buf->buf_current_csn, cscb->local_maxcsn) == 0 &&
+ (csn_get_seqnum(buf->buf_current_csn) ==
+ csn_get_seqnum(cscb->local_maxcsn) + 1) ) {
+ csn_init_by_csn ( cscb->local_maxcsn, buf->buf_current_csn );
+ csn_init_by_csn ( cscb->consumer_maxcsn, buf->buf_current_csn );
+ skip = 0;
+ break;
+ }
+
+ /* Skip CSNs not covered by local RUV snapshot */
+ cscb->state = CLC_STATE_CSN_GT_RUV;
+
+ } while (0);
+
+#ifdef DEBUG
+ if (skip && cscb) {
+ char consumer[24] = {'\0'};
+ char local[24] = {'\0'};
+ char current[24] = {'\0'};
+
+ if ( cscb->consumer_maxcsn )
+ csn_as_string ( cscb->consumer_maxcsn, PR_FALSE, consumer );
+ if ( cscb->local_maxcsn )
+ csn_as_string ( cscb->local_maxcsn, PR_FALSE, local );
+ csn_as_string ( buf->buf_current_csn, PR_FALSE, current );
+ slapi_log_error ( SLAPI_LOG_REPL, buf->buf_agmt_name,
+ "Skip %s consumer=%s local=%s\n", current, consumer, local );
+ }
+#endif
+
+ return skip;
+}
+
+static struct csn_seq_ctrl_block *
+clcache_new_cscb ()
+{
+ struct csn_seq_ctrl_block *cscb;
+
+ cscb = (struct csn_seq_ctrl_block *) slapi_ch_calloc ( 1, sizeof (struct csn_seq_ctrl_block) );
+ if (cscb == NULL) {
+ slapi_log_error ( SLAPI_LOG_FATAL, NULL, "clcache: malloc failure\n" );
+ }
+ return cscb;
+}
+
+static void
+clcache_free_cscb ( struct csn_seq_ctrl_block ** cscb )
+{
+ csn_free ( & (*cscb)->consumer_maxcsn );
+ csn_free ( & (*cscb)->local_maxcsn );
+ csn_free ( & (*cscb)->prev_local_maxcsn );
+ slapi_ch_free ( (void **) cscb );
+}
+
+/*
+ * Allocate and initialize a new buffer
+ * It is called when there is a request for a buffer while
+ * buffer free list is empty.
+ */
+static CLC_Buffer *
+clcache_new_buffer ( ReplicaId consumer_rid )
+{
+ CLC_Buffer *buf = NULL;
+ int page_count = 0;
+ int welldone = 0;
+ int rc = 0;
+
+ do {
+
+ buf = (CLC_Buffer*) slapi_ch_calloc (1, sizeof(CLC_Buffer));
+ if ( NULL == buf )
+ break;
+
+ buf->buf_key.flags = DB_DBT_USERMEM;
+ buf->buf_key.ulen = CSN_STRSIZE + 1;
+ buf->buf_key.size = CSN_STRSIZE;
+ buf->buf_key.data = slapi_ch_calloc( 1, buf->buf_key.ulen );
+ if ( NULL == buf->buf_key.data )
+ break;
+
+ buf->buf_data.flags = DB_DBT_USERMEM;
+ buf->buf_data.ulen = _pool->pl_buffer_default_pages * DEFAULT_CLC_BUFFER_PAGE_SIZE;
+ buf->buf_data.data = slapi_ch_malloc( buf->buf_data.ulen );
+ if ( NULL == buf->buf_data.data )
+ break;
+
+ if ( NULL == ( buf->buf_current_csn = csn_new()) )
+ break;
+
+ buf->buf_state = CLC_STATE_READY;
+ buf->buf_agmt_name = get_thread_private_agmtname();
+ buf->buf_consumer_rid = consumer_rid;
+ buf->buf_num_cscbs = 0;
+
+ welldone = 1;
+
+ } while (0);
+
+ if ( !welldone ) {
+ clcache_delete_buffer ( &buf );
+ }
+
+ return buf;
+}
+
+/*
+ * Deallocates a buffer.
+ * It is called when a buffer is returned to the buffer pool
+ * and the pool size is over the limit.
+ */
+static void
+clcache_delete_buffer ( CLC_Buffer **buf )
+{
+ if ( buf && *buf ) {
+ slapi_ch_free (&( (*buf)->buf_key.data ));
+ slapi_ch_free (&( (*buf)->buf_data.data ));
+ csn_free (&( (*buf)->buf_current_csn ));
+ csn_free (&( (*buf)->buf_missing_csn ));
+ slapi_ch_free ( (void **) buf );
+ }
+}
+
+static CLC_Busy_List *
+clcache_new_busy_list ()
+{
+ CLC_Busy_List *bl;
+ int welldone = 0;
+
+ do {
+ if ( NULL == (bl = ( CLC_Busy_List* ) slapi_ch_calloc (1, sizeof(CLC_Busy_List)) ))
+ break;
+
+ if ( NULL == (bl->bl_lock = PR_NewLock ()) )
+ break;
+
+ /*
+ if ( NULL == (bl->bl_max_csn = csn_new ()) )
+ break;
+ */
+
+ welldone = 1;
+ }
+ while (0);
+
+ if ( !welldone ) {
+ clcache_delete_busy_list ( &bl );
+ }
+
+ return bl;
+}
+
+static void
+clcache_delete_busy_list ( CLC_Busy_List **bl )
+{
+ if ( bl && *bl ) {
+ if ( (*bl)->bl_lock ) {
+ PR_DestroyLock ( (*bl)->bl_lock );
+ }
+ /* csn_free (&( (*bl)->bl_max_csn )); */
+ slapi_ch_free ( (void **) bl );
+ }
+}
+
+static int
+clcache_enqueue_busy_list ( DB *db, CLC_Buffer *buf )
+{
+ CLC_Busy_List *bl;
+ int rc = 0;
+
+ PR_RWLock_Rlock ( _pool->pl_lock );
+ for ( bl = _pool->pl_busy_lists; bl && bl->bl_db != db; bl = bl->bl_next );
+ PR_RWLock_Unlock ( _pool->pl_lock );
+
+ if ( NULL == bl ) {
+ if ( NULL == ( bl = clcache_new_busy_list ()) ) {
+ rc = ENOMEM;
+ }
+ else {
+ PR_RWLock_Wlock ( _pool->pl_lock );
+ bl->bl_db = db;
+ bl->bl_next = _pool->pl_busy_lists;
+ _pool->pl_busy_lists = bl;
+ PR_RWLock_Unlock ( _pool->pl_lock );
+ }
+ }
+
+ if ( NULL != bl ) {
+ PR_Lock ( bl->bl_lock );
+ buf->buf_busy_list = bl;
+ buf->buf_next = bl->bl_buffers;
+ bl->bl_buffers = buf;
+ PR_Unlock ( bl->bl_lock );
+ }
+
+ return rc;
+}
+
+static int
+clcache_open_cursor ( DB_TXN *txn, CLC_Buffer *buf, DBC **cursor )
+{
+ int rc;
+
+ rc = buf->buf_busy_list->bl_db->cursor ( buf->buf_busy_list->bl_db, txn, cursor, 0 );
+ if ( rc != 0 ) {
+ slapi_log_error ( SLAPI_LOG_FATAL, get_thread_private_agmtname(),
+ "clcache: failed to open cursor; db error - %d %s\n",
+ rc, db_strerror(rc));
+ }
+
+ return rc;
+}
+
+static int
+clcache_cursor_get ( DBC *cursor, CLC_Buffer *buf, int flag )
+{
+ int rc;
+
+ rc = cursor->c_get ( cursor,
+ & buf->buf_key,
+ & buf->buf_data,
+ buf->buf_load_flag | flag );
+ if ( ENOMEM == rc ) {
+ /*
+ * The record takes more space than the current size of the
+ * buffer. Fortunately, buf->buf_data.size has been set by
+ * c_get() to the actual data size needed. So we can
+ * reallocate the data buffer and try to read again.
+ */
+ buf->buf_data.ulen = ( buf->buf_data.size / DEFAULT_CLC_BUFFER_PAGE_SIZE + 1 ) * DEFAULT_CLC_BUFFER_PAGE_SIZE;
+ buf->buf_data.data = slapi_ch_realloc ( buf->buf_data.data, buf->buf_data.ulen );
+ if ( buf->buf_data.data != NULL ) {
+ rc = cursor->c_get ( cursor,
+ &( buf->buf_key ),
+ &( buf->buf_data ),
+ buf->buf_load_flag | flag );
+ slapi_log_error ( SLAPI_LOG_REPL, buf->buf_agmt_name,
+ "clcache: (%d | %d) %s reallocated and retry returns %d\n", buf->buf_load_flag, flag, buf->buf_key.data, rc );
+ }
+ }
+
+ switch ( rc ) {
+ case EINVAL:
+ slapi_log_error ( SLAPI_LOG_FATAL, buf->buf_agmt_name,
+ "clcache_cursor_get: invalid parameter\n" );
+ break;
+
+ case ENOMEM:
+ slapi_log_error ( SLAPI_LOG_FATAL, buf->buf_agmt_name,
+ "clcache_cursor_get: cann't allocate %u bytes\n", buf->buf_data.ulen );
+ break;
+
+ default:
+ break;
+ }
+
+ return rc;
+}
+
+static void
+csn_dup_or_init_by_csn ( CSN **csn1, CSN *csn2 )
+{
+ if ( *csn1 == NULL )
+ *csn1 = csn_new();
+ csn_init_by_csn ( *csn1, csn2 );
+}
diff --git a/ldap/servers/plugins/replication/cl5_clcache.h b/ldap/servers/plugins/replication/cl5_clcache.h
new file mode 100644
index 00000000..93024d1e
--- /dev/null
+++ b/ldap/servers/plugins/replication/cl5_clcache.h
@@ -0,0 +1,22 @@
+#ifndef CL5_CLCACHE_H
+#define CL5_CLCACHE_H
+
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#include "db.h"
+#include "slapi-private.h"
+
+typedef struct clc_buffer CLC_Buffer;
+
+int clcache_init ( DB_ENV **dbenv );
+void clcache_set_config ( CL5DBConfig * config );
+int clcache_get_buffer ( CLC_Buffer **buf, DB *db, ReplicaId consumer_rid, const RUV *consumer_ruv, const RUV *local_ruv );
+int clcache_load_buffer ( CLC_Buffer *buf, CSN *startCSN, int flag );
+void clcache_return_buffer ( CLC_Buffer **buf );
+int clcache_get_next_change ( CLC_Buffer *buf, void **key, size_t *keylen, void **data, size_t *datalen, CSN **csn );
+
+#endif
diff --git a/ldap/servers/plugins/replication/cl5_config.c b/ldap/servers/plugins/replication/cl5_config.c
new file mode 100644
index 00000000..58c79dc1
--- /dev/null
+++ b/ldap/servers/plugins/replication/cl5_config.c
@@ -0,0 +1,868 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* cl5_config.c - functions to process changelog configuration
+ */
+
+#include <string.h>
+#include <prio.h>
+#include "repl5.h"
+#include "cl5.h"
+#include "cl5_clcache.h" /* To configure the Changelog Cache */
+#include "intrinsics.h" /* JCMREPL - Is this bad? */
+#ifdef TEST_CL5
+#include "cl5_test.h"
+#endif
+
+#define CONFIG_BASE "cn=changelog5,cn=config" /*"cn=changelog,cn=supplier,cn=replication5.0,cn=replication,cn=config"*/
+#define CONFIG_FILTER "(objectclass=*)"
+
+static PRRWLock *s_configLock; /* guarantees that only on thread at a time
+ modifies changelog configuration */
+
+/* Forward Declartions */
+static int changelog5_config_add (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg);
+static int changelog5_config_modify (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg);
+static int changelog5_config_delete (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg);
+static int dont_allow_that(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, int *returncode, char *returntext, void *arg);
+
+static void changelog5_extract_config(Slapi_Entry* entry, changelog5Config *config);
+static changelog5Config * changelog5_dup_config(changelog5Config *config);
+static void replace_bslash (char *dir);
+static int notify_replica (Replica *r, void *arg);
+static int _is_absolutepath (char *dir);
+
+int changelog5_config_init()
+{
+ /* The FE DSE *must* be initialised before we get here */
+
+ /* create the configuration lock */
+ s_configLock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "config_lock");
+ if (s_configLock == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "changelog5_config_init: failed to create configurationlock; "
+ "NSPR error - %d\n",PR_GetError ());
+ return 1;
+ }
+
+ slapi_config_register_callback(SLAPI_OPERATION_ADD, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_BASE,
+ CONFIG_FILTER, changelog5_config_add, NULL);
+ slapi_config_register_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_BASE,
+ CONFIG_FILTER, changelog5_config_modify, NULL);
+ slapi_config_register_callback(SLAPI_OPERATION_MODRDN, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_BASE,
+ CONFIG_FILTER, dont_allow_that, NULL);
+ slapi_config_register_callback(SLAPI_OPERATION_DELETE, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_BASE,
+ CONFIG_FILTER, changelog5_config_delete, NULL);
+
+ return 0;
+}
+
+void changelog5_config_cleanup()
+{
+ slapi_config_remove_callback(SLAPI_OPERATION_ADD, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_BASE,
+ CONFIG_FILTER, changelog5_config_add);
+ slapi_config_remove_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_BASE,
+ CONFIG_FILTER, changelog5_config_modify);
+ slapi_config_remove_callback(SLAPI_OPERATION_MODRDN, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_BASE,
+ CONFIG_FILTER, dont_allow_that);
+ slapi_config_remove_callback(SLAPI_OPERATION_DELETE, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_BASE,
+ CONFIG_FILTER, changelog5_config_delete);
+
+ if (s_configLock)
+ {
+ PR_DestroyRWLock (s_configLock);
+ s_configLock = NULL;
+ }
+}
+
+int changelog5_read_config (changelog5Config *config)
+{
+ int rc = LDAP_SUCCESS;
+ Slapi_PBlock *pb;
+
+ pb = slapi_pblock_new ();
+ slapi_search_internal_set_pb (pb, CONFIG_BASE, LDAP_SCOPE_BASE, CONFIG_FILTER, NULL, 0, NULL,
+ NULL, repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0);
+ slapi_search_internal_pb (pb);
+ slapi_pblock_get( pb, SLAPI_PLUGIN_INTOP_RESULT, &rc );
+ if ( LDAP_SUCCESS == rc )
+ {
+ Slapi_Entry **entries = NULL;
+ slapi_pblock_get( pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries );
+ if ( NULL != entries && NULL != entries[0])
+ {
+ /* Extract the config info from the changelog entry */
+ changelog5_extract_config(entries[0], config);
+ }
+ }
+ else
+ {
+ memset (config, 0, sizeof (*config));
+ rc = LDAP_SUCCESS;
+ }
+
+ slapi_free_search_results_internal(pb);
+ slapi_pblock_destroy(pb);
+
+ return rc;
+}
+
+void changelog5_config_done (changelog5Config *config)
+{
+ if (config) {
+ /* slapi_ch_free_string accepts NULL pointer */
+ slapi_ch_free_string (&config->maxAge);
+ slapi_ch_free_string (&config->dir);
+ }
+}
+
+void changelog5_config_free (changelog5Config **config)
+{
+ changelog5_config_done(*config);
+ slapi_ch_free((void **)config);
+}
+
+static int
+changelog5_config_add (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter,
+ int *returncode, char *returntext, void *arg)
+{
+ int rc;
+ changelog5Config config;
+
+ *returncode = LDAP_SUCCESS;
+
+ PR_RWLock_Wlock (s_configLock);
+
+ /* we already have a configured changelog - don't need to do anything
+ since add operation will fail */
+ if (cl5GetState () == CL5_STATE_OPEN)
+ {
+ *returncode = 1;
+ if (returntext)
+ {
+ strcpy (returntext, "attempt to add changelog when it already exists");
+ }
+
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "changelog5_config_add: changelog already exist; "
+ "request ignored\n");
+ goto done;
+ }
+
+ changelog5_extract_config(e, &config);
+ if (config.dir == NULL)
+ {
+ *returncode = 1;
+ if (returntext)
+ {
+ sprintf (returntext, "NULL changelog directory");
+ }
+
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "changelog5_config_add: NULL changelog directory\n");
+ goto done;
+ }
+
+ /* start the changelog */
+ rc = cl5Open (config.dir, &config.dbconfig);
+ if (rc != CL5_SUCCESS)
+ {
+ *returncode = 1;
+ if (returntext)
+ {
+ sprintf (returntext, "failed to start changelog; error - %d", rc);
+ }
+
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "changelog5_config_add: failed to start changelog\n");
+ goto done;
+ }
+
+ /* set trimming parameters */
+ rc = cl5ConfigTrimming (config.maxEntries, config.maxAge);
+ if (rc != CL5_SUCCESS)
+ {
+ *returncode = 1;
+ if (returntext)
+ {
+ sprintf (returntext, "failed to configure changelog trimming; error - %d", rc);
+ }
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "changelog5_config_add: failed to configure changelog trimming\n");
+ goto done;
+ }
+
+ /* notify all the replicas that the changelog is configured
+ so that the can log dummy changes if necessary. */
+ replica_enumerate_replicas (notify_replica, NULL);
+
+#ifdef TEST_CL5
+ testChangelog (TEST_ITERATION);
+#endif
+
+done:;
+ PR_RWLock_Unlock (s_configLock);
+ changelog5_config_done (&config);
+ if (*returncode == LDAP_SUCCESS)
+ {
+ if (returntext)
+ {
+ returntext[0] = '\0';
+ }
+
+ return SLAPI_DSE_CALLBACK_OK;
+ }
+
+ return SLAPI_DSE_CALLBACK_ERROR;
+}
+
+static int
+changelog5_config_modify (Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e,
+ int *returncode, char *returntext, void *arg)
+{
+ int rc= 0;
+ LDAPMod **mods;
+ int i;
+ changelog5Config config;
+ changelog5Config * originalConfig = NULL;
+ char *currentDir = NULL;
+
+ *returncode = LDAP_SUCCESS;
+
+ /* changelog must be open before its parameters can be modified */
+ if (cl5GetState() != CL5_STATE_OPEN)
+ {
+ if (returntext)
+ {
+ strcpy (returntext, "changelog is not configured");
+ }
+
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "changelog5_config_modify: changelog is not configured\n");
+ return SLAPI_DSE_CALLBACK_ERROR;
+ }
+
+ PR_RWLock_Wlock (s_configLock);
+
+ /* changelog must be open before its parameters can be modified */
+ if (cl5GetState() != CL5_STATE_OPEN)
+ {
+ *returncode = 1;
+ if (returntext)
+ {
+ strcpy (returntext, "changelog is not configured");
+ }
+
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "changelog5_config_modify: changelog is not configured\n");
+ goto done;
+ }
+
+ /*
+ * Extract all the original configuration: This is needed to ensure that the configuration
+ * is trully reloaded. This was not needed before 091401 because the changelog configuration
+ * was always hardcoded (NULL was being passed to cl5Open). Now we need to ensure we pass to
+ * cl5Open the proper configuration...
+ */
+ changelog5_extract_config(e, &config);
+ originalConfig = changelog5_dup_config(&config);
+
+ /* Reset all the attributes that have been potentially modified by the current MODIFY operation */
+ slapi_ch_free_string(&config.dir);
+ config.dir = NULL;
+ config.maxEntries = CL5_NUM_IGNORE;
+ slapi_ch_free_string(&config.maxAge);
+ config.maxAge = slapi_ch_strdup(CL5_STR_IGNORE);
+ config.dbconfig.maxChCacheEntries = 0;
+ config.dbconfig.maxChCacheSize = CL5_NUM_IGNORE;
+
+ slapi_pblock_get( pb, SLAPI_MODIFY_MODS, &mods );
+ for (i = 0; mods[i] != NULL; i++)
+ {
+ if (mods[i]->mod_op & LDAP_MOD_DELETE)
+ {
+ /* We don't support deleting changelog attributes */
+ }
+ else
+ {
+ int j;
+ for (j = 0; ((mods[i]->mod_values[j]) && (LDAP_SUCCESS == rc)); j++)
+ {
+ char *config_attr, *config_attr_value;
+ config_attr = (char *) mods[i]->mod_type;
+ config_attr_value = (char *) mods[i]->mod_bvalues[j]->bv_val;
+
+#define ATTR_MODIFIERSNAME "modifiersname"
+#define ATTR_MODIFYTIMESTAMP "modifytimestamp"
+
+ if ( strcasecmp ( config_attr, ATTR_MODIFIERSNAME ) == 0 ) {
+ continue;
+ }
+ if ( strcasecmp ( config_attr, ATTR_MODIFYTIMESTAMP ) == 0 ) {
+ continue;
+ }
+ /* replace existing value */
+ if ( strcasecmp (config_attr, CONFIG_CHANGELOG_DIR_ATTRIBUTE ) == 0 )
+ {
+ if (config_attr_value && config_attr_value[0] != '\0')
+ {
+ slapi_ch_free_string(&config.dir);
+ config.dir = slapi_ch_strdup(config_attr_value);
+ replace_bslash (config.dir);
+ }
+ else
+ {
+ *returncode = 1;
+ if (returntext)
+ {
+ strcpy (returntext, "null changelog directory");
+ }
+ goto done;
+ }
+ }
+ else if ( strcasecmp ( config_attr, CONFIG_CHANGELOG_MAXENTRIES_ATTRIBUTE ) == 0 )
+ {
+ if (config_attr_value && config_attr_value[0] != '\0')
+ {
+ config.maxEntries = atoi (config_attr_value);
+ }
+ else
+ {
+ config.maxEntries = 0;
+ }
+ }
+ else if ( strcasecmp ( config_attr, CONFIG_CHANGELOG_MAXAGE_ATTRIBUTE ) == 0 )
+ {
+ slapi_ch_free_string(&config.maxAge);
+ config.maxAge = slapi_ch_strdup(config_attr_value);
+ }
+ else if ( strcasecmp ( config_attr, CONFIG_CHANGELOG_CACHESIZE ) == 0 )
+ { /* The Changelog Cache Size parameters can be modified online without a need for restart */
+ if (config_attr_value && config_attr_value[0] != '\0')
+ {
+ config.dbconfig.maxChCacheEntries = atoi (config_attr_value);
+ }
+ else
+ {
+ config.dbconfig.maxChCacheEntries = 0;
+ }
+ }
+ else if ( strcasecmp ( config_attr, CONFIG_CHANGELOG_CACHEMEMSIZE ) == 0 )
+ { /* The Changelog Cache Size parameters can be modified online without a need for restart */
+ if (config_attr_value && config_attr_value[0] != '\0')
+ {
+ config.dbconfig.maxChCacheSize = atoi (config_attr_value);
+ }
+ else
+ {
+ config.dbconfig.maxChCacheSize = 0;
+ }
+ }
+ else
+ {
+ *returncode = LDAP_UNWILLING_TO_PERFORM;
+ if (returntext)
+ {
+ sprintf (returntext, "Unwilling to apply %s mods while the server is running", config_attr);
+ }
+ goto done;
+ }
+ }
+ }
+ }
+ /* Undo the reset above for all the modifiable attributes that were not modified
+ * except config.dir */
+ if (config.maxEntries == CL5_NUM_IGNORE)
+ config.maxEntries = originalConfig->maxEntries;
+ if (strcmp (config.maxAge, CL5_STR_IGNORE) == 0) {
+ slapi_ch_free_string(&config.maxAge);
+ if (originalConfig->maxAge)
+ config.maxAge = slapi_ch_strdup(originalConfig->maxAge);
+ }
+ if (config.dbconfig.maxChCacheEntries == 0)
+ config.dbconfig.maxChCacheEntries = originalConfig->dbconfig.maxChCacheEntries;
+ if (config.dbconfig.maxChCacheSize == CL5_NUM_IGNORE)
+ config.dbconfig.maxChCacheSize = originalConfig->dbconfig.maxChCacheSize;
+
+
+ /* attempt to change chagelog dir */
+ if (config.dir)
+ {
+ currentDir = cl5GetDir ();
+ if (currentDir == NULL)
+ {
+ /* something is wrong: we should never be here */
+ *returncode = 1;
+ if (returntext)
+ {
+ strcpy (returntext, "internal failure");
+ }
+
+ goto done;
+ }
+
+#ifdef _WIN32
+ if (strcasecmp (currentDir, config.dir) != 0)
+#else /* On Unix, path are case sensitive */
+ if (strcmp (currentDir, config.dir) != 0)
+#endif
+ {
+ if (!_is_absolutepath(config.dir) || (CL5_SUCCESS != cl5CreateDirIfNeeded(config.dir)))
+ {
+ *returncode = 1;
+ if (returntext)
+ {
+ strcpy (returntext, "invalid changelog directory or insufficient access");
+ }
+
+ goto done;
+ }
+
+ /* changelog directory changed - need to remove the
+ previous changelog and create new one */
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl,
+ "changelog5_config_modify: changelog directory changed; "
+ "old dir - %s, new dir - %s; recreating changelog.\n",
+ currentDir, config.dir);
+
+ /* this call will block until all threads using changelog
+ release changelog by calling cl5RemoveThread () */
+ rc = cl5Close ();
+ if (rc != CL5_SUCCESS)
+ {
+ *returncode = 1;
+ if (returntext)
+ {
+ sprintf (returntext, "failed to close changelog; error - %d", rc);
+ }
+
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "changelog5_config_modify: failed to close changelog\n");
+ goto done;
+ }
+
+ rc = cl5Delete (currentDir);
+ if (rc != CL5_SUCCESS)
+ {
+ *returncode = 1;
+ if (returntext)
+ {
+ sprintf (returntext, "failed to remove changelog; error - %d", rc);
+ }
+
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "changelog5_config_modify: failed to remove changelog\n");
+ goto done;
+ }
+
+ rc = cl5Open (config.dir, &config.dbconfig);
+ if (rc != CL5_SUCCESS)
+ {
+ *returncode = 1;
+ if (returntext)
+ {
+ sprintf (returntext, "failed to restart changelog; error - %d", rc);
+ }
+
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "changelog5_config_modify: failed to restart changelog\n");
+ /* before finishing, let's try to do some error recovery */
+ if (CL5_SUCCESS != cl5Open(currentDir, &config.dbconfig)) {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "changelog5_config_modify: failed to restore previous changelog\n");
+ }
+ goto done;
+ }
+ }
+ }
+
+ /* one of the changelog parameters is modified */
+ if (config.maxEntries != CL5_NUM_IGNORE ||
+ strcmp (config.maxAge, CL5_STR_IGNORE) != 0)
+ {
+ rc = cl5ConfigTrimming (config.maxEntries, config.maxAge);
+ if (rc != CL5_SUCCESS)
+ {
+ *returncode = 1;
+ if (returntext)
+ {
+ sprintf (returntext, "failed to configure changelog trimming; error - %d", rc);
+ }
+
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "changelog5_config_modify: failed to configure changelog trimming\n");
+ goto done;
+ }
+ }
+
+ if (config.dbconfig.maxChCacheEntries != 0 || config.dbconfig.maxChCacheSize != CL5_NUM_IGNORE)
+ clcache_set_config(&config.dbconfig);
+
+done:;
+ PR_RWLock_Unlock (s_configLock);
+
+ changelog5_config_done (&config);
+ changelog5_config_free (&originalConfig);
+
+ /* slapi_ch_free accepts NULL pointer */
+ slapi_ch_free ((void**)&currentDir);
+
+ if (*returncode == LDAP_SUCCESS)
+ {
+
+ if (returntext)
+ {
+ returntext[0] = '\0';
+ }
+
+ return SLAPI_DSE_CALLBACK_OK;
+ }
+
+ return SLAPI_DSE_CALLBACK_ERROR;
+}
+
+static int
+changelog5_config_delete (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter,
+ int *returncode, char *returntext, void *arg)
+{
+ int rc;
+ char *currentDir = NULL;
+ *returncode = LDAP_SUCCESS;
+
+ /* changelog must be open before it can be deleted */
+ if (cl5GetState () != CL5_STATE_OPEN)
+ {
+ *returncode = 1;
+ if (returntext)
+ {
+ strcpy (returntext, "changelog is not configured");
+ }
+
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "changelog5_config_delete: chagelog is not configured\n");
+ return SLAPI_DSE_CALLBACK_ERROR;
+ }
+
+ PR_RWLock_Wlock (s_configLock);
+
+ /* changelog must be open before it can be deleted */
+ if (cl5GetState () != CL5_STATE_OPEN)
+ {
+ *returncode = 1;
+ if (returntext)
+ {
+ strcpy (returntext, "changelog is not configured");
+ }
+
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "changelog5_config_delete: chagelog is not configured\n");
+ goto done;
+ }
+
+ currentDir = cl5GetDir ();
+
+ if (currentDir == NULL)
+ {
+ /* something is wrong: we should never be here */
+ *returncode = 1;
+ if (returntext)
+ {
+ strcpy (returntext, "internal failure");
+ }
+
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "changelog5_config_delete: NULL directory\n");
+ goto done;
+ }
+
+ /* this call will block until all threads using changelog
+ release changelog by calling cl5RemoveThread () */
+ rc = cl5Close ();
+ if (rc != CL5_SUCCESS)
+ {
+ *returncode = 1;
+ if (returntext)
+ {
+ sprintf (returntext, "failed to close changelog; error - %d", rc);
+ }
+
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "changelog5_config_delete: failed to close changelog\n");
+ goto done;
+ }
+
+ rc = cl5Delete (currentDir);
+ if (rc != CL5_SUCCESS)
+ {
+ *returncode = 1;
+ if (returntext)
+ {
+ sprintf (returntext, "failed to remove changelog; error - %d", rc);
+ }
+
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "changelog5_config_delete: failed to remove changelog\n");
+ goto done;
+ }
+
+done:;
+ PR_RWLock_Unlock (s_configLock);
+
+ /* slapi_ch_free accepts NULL pointer */
+ slapi_ch_free ((void**)&currentDir);
+
+ if (*returncode == LDAP_SUCCESS)
+ {
+ if (returntext)
+ {
+ returntext[0] = '\0';
+ }
+
+ return SLAPI_DSE_CALLBACK_OK;
+ }
+
+ return SLAPI_DSE_CALLBACK_ERROR;
+}
+
+static int dont_allow_that(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e,
+ int *returncode, char *returntext, void *arg)
+{
+ *returncode = LDAP_UNWILLING_TO_PERFORM;
+ return SLAPI_DSE_CALLBACK_ERROR;
+}
+
+static changelog5Config * changelog5_dup_config(changelog5Config *config)
+{
+ changelog5Config *dup = (changelog5Config *) slapi_ch_calloc(1, sizeof(changelog5Config));
+
+ if (config->dir)
+ dup->dir = slapi_ch_strdup(config->dir);
+ if (config->maxAge)
+ dup->maxAge = slapi_ch_strdup(config->maxAge);
+
+ dup->maxEntries = config->maxEntries;
+
+ /*memcpy((void *) &dup->dbconfig, (const void *) &config->dbconfig, sizeof(CL5DBConfig));*/
+ dup->dbconfig.cacheSize = config->dbconfig.cacheSize;
+ dup->dbconfig.durableTrans = config->dbconfig.durableTrans;
+ dup->dbconfig.checkpointInterval = config->dbconfig.checkpointInterval;
+ dup->dbconfig.circularLogging = config->dbconfig.circularLogging;
+ dup->dbconfig.pageSize = config->dbconfig.pageSize;
+ dup->dbconfig.logfileSize = config->dbconfig.logfileSize;
+ dup->dbconfig.maxTxnSize = config->dbconfig.maxTxnSize;
+ dup->dbconfig.fileMode = config->dbconfig.fileMode;
+ dup->dbconfig.verbose = config->dbconfig.verbose;
+ dup->dbconfig.debug = config->dbconfig.debug;
+ dup->dbconfig.tricklePercentage = config->dbconfig.tricklePercentage;
+ dup->dbconfig.spinCount = config->dbconfig.spinCount;
+ dup->dbconfig.maxChCacheEntries = config->dbconfig.maxChCacheEntries;
+ dup->dbconfig.maxChCacheSize = config->dbconfig.maxChCacheSize;
+ dup->dbconfig.nb_lock_config = config->dbconfig.nb_lock_config;
+
+ return dup;
+}
+
+
+/*
+ * Given the changelog configuration entry, extract the configuration directives.
+ */
+static void changelog5_extract_config(Slapi_Entry* entry, changelog5Config *config)
+{
+ char *arg;
+
+ memset (config, 0, sizeof (*config));
+ config->dir = slapi_entry_attr_get_charptr(entry,CONFIG_CHANGELOG_DIR_ATTRIBUTE);
+ replace_bslash (config->dir);
+
+ arg= slapi_entry_attr_get_charptr(entry,CONFIG_CHANGELOG_MAXENTRIES_ATTRIBUTE);
+ if (arg)
+ {
+ config->maxEntries = atoi (arg);
+ slapi_ch_free_string(&arg);
+ }
+
+ config->maxAge = slapi_entry_attr_get_charptr(entry,CONFIG_CHANGELOG_MAXAGE_ATTRIBUTE);
+
+ /*
+ * Read the Changelog Internal Configuration Parameters for the Changelog DB
+ * (db cache size, db settings...)
+ */
+
+ /* Set configuration default values first... */
+ config->dbconfig.cacheSize = CL5_DEFAULT_CONFIG_DB_DBCACHESIZE;
+ config->dbconfig.durableTrans = CL5_DEFAULT_CONFIG_DB_DURABLE_TRANSACTIONS;
+ config->dbconfig.checkpointInterval = CL5_DEFAULT_CONFIG_DB_CHECKPOINT_INTERVAL;
+ config->dbconfig.circularLogging = CL5_DEFAULT_CONFIG_DB_CIRCULAR_LOGGING;
+ config->dbconfig.pageSize = CL5_DEFAULT_CONFIG_DB_PAGE_SIZE;
+ config->dbconfig.logfileSize = CL5_DEFAULT_CONFIG_DB_LOGFILE_SIZE;
+ config->dbconfig.maxTxnSize = CL5_DEFAULT_CONFIG_DB_TXN_MAX;
+ config->dbconfig.verbose = CL5_DEFAULT_CONFIG_DB_VERBOSE;
+ config->dbconfig.debug = CL5_DEFAULT_CONFIG_DB_DEBUG;
+ config->dbconfig.tricklePercentage = CL5_DEFAULT_CONFIG_DB_TRICKLE_PERCENTAGE;
+ config->dbconfig.spinCount = CL5_DEFAULT_CONFIG_DB_SPINCOUNT;
+ config->dbconfig.nb_lock_config = CL5_DEFAULT_CONFIG_NB_LOCK;
+
+ /* Now read from the entry to override default values if needed */
+ arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_DB_DBCACHESIZE);
+ if (arg)
+ {
+ size_t theSize = atoi (arg);
+ if (theSize > CL5_MIN_DB_DBCACHESIZE)
+ config->dbconfig.cacheSize = theSize;
+ else {
+ config->dbconfig.cacheSize = CL5_MIN_DB_DBCACHESIZE;
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "Warning: Changelog dbcache size too small. "
+ "Increasing the Memory Size to %d bytes\n",
+ CL5_MIN_DB_DBCACHESIZE);
+ }
+ slapi_ch_free_string(&arg);
+ }
+
+ arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_DB_DURABLE_TRANSACTIONS);
+ if (arg)
+ {
+ config->dbconfig.durableTrans = atoi (arg);
+ slapi_ch_free_string(&arg);
+ }
+ arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_DB_CHECKPOINT_INTERVAL);
+ if (arg)
+ {
+ config->dbconfig.checkpointInterval = atoi (arg);
+ slapi_ch_free_string(&arg);
+ }
+ arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_DB_CIRCULAR_LOGGING);
+ if (arg)
+ {
+ config->dbconfig.circularLogging = atoi (arg);
+ slapi_ch_free_string(&arg);
+ }
+ arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_DB_PAGE_SIZE);
+ if (arg)
+ {
+ config->dbconfig.pageSize = atoi (arg);
+ slapi_ch_free_string(&arg);
+ }
+ arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_DB_LOGFILE_SIZE);
+ if (arg)
+ {
+ config->dbconfig.logfileSize = atoi (arg);
+ slapi_ch_free_string(&arg);
+ }
+ arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_DB_MAXTXN_SIZE);
+ if (arg)
+ {
+ config->dbconfig.maxTxnSize = atoi (arg);
+ slapi_ch_free_string(&arg);
+ }
+ arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_DB_VERBOSE);
+ if (arg)
+ {
+ config->dbconfig.verbose = atoi (arg);
+ slapi_ch_free_string(&arg);
+ }
+ arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_DB_DEBUG);
+ if (arg)
+ {
+ config->dbconfig.debug = atoi (arg);
+ slapi_ch_free_string(&arg);
+ }
+ arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_DB_TRICKLE_PERCENTAGE);
+ if (arg)
+ {
+ config->dbconfig.tricklePercentage = atoi (arg);
+ slapi_ch_free_string(&arg);
+ }
+ arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_DB_SPINCOUNT);
+ if (arg)
+ {
+ config->dbconfig.spinCount = atoi (arg);
+ slapi_ch_free_string(&arg);
+ }
+ arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_MAX_CONCURRENT_WRITES);
+ if (arg)
+ {
+ config->dbconfig.maxConcurrentWrites = atoi (arg);
+ slapi_ch_free_string(&arg);
+ }
+ if ( config->dbconfig.maxConcurrentWrites <= 0 )
+ {
+ config->dbconfig.maxConcurrentWrites = CL5_DEFAULT_CONFIG_MAX_CONCURRENT_WRITES;
+ }
+
+ /*
+ * Read the Changelog Internal Configuration Parameters for the Changelog Cache
+ */
+
+ /* Set configuration default values first... */
+ config->dbconfig.maxChCacheEntries = CL5_DEFAULT_CONFIG_CACHESIZE;
+ config->dbconfig.maxChCacheSize = CL5_DEFAULT_CONFIG_CACHEMEMSIZE;
+
+ /* Now read from the entry to override default values if needed */
+ arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_CACHESIZE);
+ if (arg)
+ {
+ config->dbconfig.maxChCacheEntries = atoi (arg);
+ slapi_ch_free_string(&arg);
+ }
+ arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_CACHEMEMSIZE);
+ if (arg)
+ {
+ config->dbconfig.maxChCacheSize = atoi (arg);
+ slapi_ch_free_string(&arg);
+ }
+ arg = slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_NB_LOCK);
+ if (arg)
+ {
+ size_t theSize = atoi(arg);
+ if (theSize < CL5_MIN_NB_LOCK)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "Warning: Changelog %s value is too low (%d). Set to minimal value instead (%d)\n",
+ CONFIG_CHANGELOG_NB_LOCK, theSize, CL5_MIN_NB_LOCK);
+ config->dbconfig.nb_lock_config = CL5_MIN_NB_LOCK;
+ }
+ else
+ {
+ config->dbconfig.nb_lock_config = theSize;
+ }
+ slapi_ch_free_string(&arg);
+ }
+
+ clcache_set_config(&config->dbconfig);
+}
+
+static void replace_bslash (char *dir)
+{
+ char *bslash;
+
+ if (dir == NULL)
+ return;
+
+ bslash = strchr (dir, '\\');
+ while (bslash)
+ {
+ *bslash = '/';
+ bslash = strchr (bslash, '\\');
+ }
+}
+
+static int notify_replica (Replica *r, void *arg)
+{
+ return replica_log_ruv_elements (r);
+}
+
+static int _is_absolutepath (char * dir)
+{
+ if (dir[0] == '/')
+ return 1;
+#if defined(_WIN32)
+ if (dir[2] == '/' && dir[1] == ':')
+ return 1;
+#endif
+ return 0;
+}
diff --git a/ldap/servers/plugins/replication/cl5_init.c b/ldap/servers/plugins/replication/cl5_init.c
new file mode 100644
index 00000000..435299c0
--- /dev/null
+++ b/ldap/servers/plugins/replication/cl5_init.c
@@ -0,0 +1,77 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+/* cl5_init.c - implments initialization/cleanup functions for
+ 4.0 style changelog
+ */
+
+#include "slapi-plugin.h"
+#include "cl5.h"
+#include "repl5.h"
+
+/* initializes changelog*/
+int changelog5_init()
+{
+ int rc;
+ changelog5Config config;
+
+ rc = cl5Init ();
+ if (rc != CL5_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "changelog5_init: failed to initialize changelog\n");
+ return 1;
+ }
+
+ /* read changelog configuration */
+ changelog5_config_init ();
+ changelog5_read_config (&config);
+
+ if (config.dir == NULL)
+ {
+ /* changelog is not configured - bail out */
+ rc = 0; /* OK */
+ goto done;
+ }
+
+ /* start changelog */
+ rc = cl5Open (config.dir, &config.dbconfig);
+ if (rc != CL5_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "changelog5_init: failed to start changelog at %s\n",
+ config.dir);
+ rc = 1;
+ goto done;
+ }
+
+ /* set trimming parameters */
+ rc = cl5ConfigTrimming (config.maxEntries, config.maxAge);
+ if (rc != CL5_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "changelog5_init: failed to configure changelog trimming\n");
+ rc = 1;
+ goto done;
+ }
+
+ rc = 0;
+
+done:
+ changelog5_config_done (&config);
+ return rc;
+}
+
+/* cleanups changelog data */
+void changelog5_cleanup()
+{
+ /* close changelog */
+ cl5Close ();
+ cl5Cleanup ();
+
+ /* cleanup config */
+ changelog5_config_cleanup ();
+}
diff --git a/ldap/servers/plugins/replication/cl5_test.c b/ldap/servers/plugins/replication/cl5_test.c
new file mode 100644
index 00000000..b64a60f5
--- /dev/null
+++ b/ldap/servers/plugins/replication/cl5_test.c
@@ -0,0 +1,830 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* cl5_test.c - changelog test cases */
+#include "cl5_test.h"
+#include "slapi-plugin.h"
+#include "cl5.h"
+
+#define REPLICA_ROOT "dc=example,dc=com" /* replica root */
+#define OP_COUNT 4 /* number of ops generated at a time */
+#define MOD_COUNT 5
+#define VALUE_COUNT 5
+#define ENTRY_COUNT 50
+#define CL_DN "cn=changelog5,cn=config"
+#define INSTANCE_ATTR "nsslapd-instancedir"
+#define REPLICA_OC "nsds5Replica"
+#define REPLICA_RDN "cn=replica"
+
+static void testBasic ();
+static void testBackupRestore ();
+static void testIteration ();
+static void testTrimming ();
+static void testPerformance ();
+static void testPerformanceMT ();
+static void testLDIF ();
+static void testAll ();
+static int configureChangelog ();
+static int configureReplica ();
+static int populateChangelogOp ();
+static int populateChangelog (int entryCount, CSN ***csnList);
+static int processEntries (int entryCount, CSN **csnList);
+static void clearCSNList (CSN ***csnList, int count);
+static void threadMain (void *data);
+static char* getBaseDir (const char *dir);
+static LDAPMod **buildMods ();
+
+void testChangelog (TestType type)
+{
+ switch (type)
+ {
+ case TEST_BASIC: testBasic ();
+ break;
+ case TEST_BACKUP_RESTORE: testBackupRestore ();
+ break;
+ case TEST_ITERATION: testIteration ();
+ break;
+ case TEST_TRIMMING: testTrimming ();
+ break;
+ case TEST_PERFORMANCE: testPerformance ();
+ break;
+ case TEST_PERFORMANCE_MT: testPerformanceMT ();
+ break;
+ case TEST_LDIF: testLDIF ();
+ break;
+ case TEST_ALL: testAll ();
+ break;
+ default: printf ("Taste case %d is not supported\n", type);
+ }
+}
+
+/* tests Open/Close, normal recovery, read/write/remove
+ of an entry */
+static void testBasic ()
+{
+ int rc = 0;
+
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Starting basic test ...\n");
+
+ /* ONREPL - we can't run the tests from the startup code because
+ operations can't be issued until all plugins are started. So,
+ instead, we do it when changelog is created
+ rc = configureChangelog (); */
+ if (rc == 0)
+ {
+ rc = configureReplica ();
+ if (rc == 0)
+ {
+ rc = populateChangelogOp ();
+ }
+ }
+
+ if (rc == 0)
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "Basic test completed successfully\n");
+ else
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "Basic test failed\n");
+}
+
+static void testBackupRestore ()
+{
+ char *dir;
+ int rc = -1;
+ char *baseDir;
+ char bkDir [MAXPATHLEN + 1];
+
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Starting backup and recovery test ...\n");
+
+ dir = cl5GetDir ();
+
+ if (dir)
+ {
+ baseDir = getBaseDir (dir);
+ sprintf (bkDir, "%s/clbackup", baseDir);
+ slapi_ch_free ((void**)&baseDir);
+ rc = cl5Backup (bkDir, NULL);
+
+ if (rc == CL5_SUCCESS)
+ {
+ cl5Close ();
+ rc = cl5Restore (dir, bkDir, NULL);
+ if (rc == CL5_SUCCESS)
+ rc = cl5Open (dir, NULL);
+
+ /* PR_RmDir (bkDir);*/
+ }
+ }
+
+ if (rc == CL5_SUCCESS)
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "Backup and Restore test completed successfully\n");
+ else
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "Backup and Restore test failed\n");
+}
+
+static void testIteration ()
+{
+ Object *r_obj;
+ Slapi_DN *r_root;
+ Replica *r;
+ char *replGen;
+ RUV *ruv;
+ CL5ReplayIterator *it = NULL;
+ slapi_operation_parameters op;
+ int rc;
+ int i;
+
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Starting iteration test ...\n");
+
+ /* get replica object */
+ r_root = slapi_sdn_new_dn_byval(REPLICA_ROOT);
+ r_obj = replica_get_replica_from_dn (r_root);
+ slapi_sdn_free (&r_root);
+ if (r_obj == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "replica is not configured for (%s)\n",
+ REPLICA_ROOT);
+ return;
+ }
+
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Starting first iteration pass ...\n");
+
+ /* configure empty consumer ruv */
+ r = (Replica*)object_get_data (r_obj);
+ PR_ASSERT (r);
+ replGen = replica_get_generation (r);
+ ruv_init_new (replGen, 0, NULL, &ruv);
+
+ /* create replay iterator */
+ rc = cl5CreateReplayIterator (r_obj, ruv, &it);
+ if (it)
+ {
+ i = 0;
+ while ((rc = cl5GetNextOperationToReplay (it, &op)) == CL5_SUCCESS)
+ {
+ ruv_set_csns (ruv, op.csn, NULL);
+ operation_parameters_done (&op);
+ i ++;
+ }
+ }
+
+ if (it)
+ cl5DestroyReplayIterator (&it);
+
+ if (rc == CL5_NOTFOUND)
+ {
+ if (i == 0) /* success */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "First iteration pass completed "
+ "successfully: no changes to replay\n");
+ else /* incorrect number of entries traversed */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "First iteration pass failed: "
+ "traversed %d entries; expected none\n", i);
+ }
+ else /* general error */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "First iteration pass failed\n");
+
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Starting second iteration pass ...\n");
+
+ /* add some entries */
+ populateChangelogOp ();
+
+ /* create replay iterator */
+ rc = cl5CreateReplayIterator (r_obj, ruv, &it);
+ if (it)
+ {
+ i = 0;
+ while ((rc = cl5GetNextOperationToReplay (it, &op)) == CL5_SUCCESS)
+ {
+ ruv_set_csns (ruv, op.csn, NULL);
+ operation_parameters_done (&op);
+ i ++;
+ }
+ }
+
+ if (it)
+ cl5DestroyReplayIterator (&it);
+
+ if (rc == CL5_NOTFOUND)
+ {
+ if (i == OP_COUNT) /* success */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Second iteration pass completed "
+ "successfully: %d entries traversed\n", i);
+ else /* incorrect number of entries traversed */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Second iteration pass failed: "
+ "traversed %d entries; expected %d\n", i, OP_COUNT);
+ }
+ else /* general error */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Second iteration pass failed\n");
+
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Starting third iteration pass ...\n");
+ /* add more entries */
+ populateChangelogOp ();
+
+ /* create replay iterator */
+ rc = cl5CreateReplayIterator (r_obj, ruv, &it);
+ if (it)
+ {
+ i = 0;
+ while ((rc = cl5GetNextOperationToReplay (it, &op)) == CL5_SUCCESS)
+ {
+ ruv_set_csns (ruv, op.csn, NULL);
+ operation_parameters_done (&op);
+ i ++;
+ }
+ }
+
+ if (it)
+ cl5DestroyReplayIterator (&it);
+
+ if (rc == CL5_NOTFOUND)
+ {
+ if (i == OP_COUNT) /* success */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Third iteration pass completed "
+ "successfully: %d entries traversed\n", i);
+ else /* incorrect number of entries traversed */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Third iteration pass failed: "
+ "traversed %d entries; expected %d\n", i, OP_COUNT);
+ }
+ else /* general error */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Second iteration pass failed\n");
+
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Iteration test is complete\n");
+
+ ruv_destroy (&ruv);
+ object_release (r_obj);
+ slapi_ch_free ((void**)&replGen);
+}
+
+static void testTrimming ()
+{
+ PRIntervalTime interval;
+ int count;
+ int rc;
+
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Starting trimming test ...\n");
+
+ rc = populateChangelog (200, NULL);
+
+ if (rc == 0)
+ {
+ interval = PR_SecondsToInterval(2);
+ DS_Sleep (interval);
+
+ rc = populateChangelog (300, NULL);
+
+ if (rc == 0)
+ rc = cl5ConfigTrimming (300, "1d");
+
+ interval = PR_SecondsToInterval(300); /* 5 min is default trimming interval */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "Trimming test: sleeping for 5 minutes until trimming kicks in\n");
+ DS_Sleep (interval);
+
+ count = cl5GetOperationCount (NULL);
+ }
+
+ if (rc == 0 && count == 300)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "Trimming test completed successfully: changelog contains 300 entries\n");
+ }
+ else if (rc == 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "Trimming test failed: changelog contains %d entries; expected - 300\n",
+ count);
+ }
+ else /* general failure */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Trimming test failed\n");
+}
+
+static void testPerformance ()
+{
+ PRTime starttime, endtime, totaltime;
+ int entryCount = 5000;
+ CSN **csnList = NULL;
+ int rc;
+
+ starttime = PR_Now();
+
+ rc = populateChangelog (entryCount, &csnList);
+
+ endtime = PR_Now();
+
+ totaltime = (endtime - starttime) / 1000; /* ms */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Write performance:\n"
+ "entry count - %d, total time - %ldms\n"
+ "latency = %d msec\nthroughput = %d entry/sec\n",
+ entryCount, totaltime,
+ totaltime / entryCount, entryCount * 1000 / totaltime);
+
+
+ starttime = endtime;
+
+ rc = processEntries (entryCount, csnList);
+
+ endtime = PR_Now();
+
+ totaltime = (endtime - starttime) / 1000; /* ms */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Read performance:\n"
+ "entry count - %d, total time - %ld\n"
+ "latency = %d msec\nthroughput = %d entry/sec\n",
+ entryCount, totaltime,
+ totaltime / entryCount, entryCount * 1000 / totaltime);
+
+ clearCSNList (&csnList, entryCount);
+}
+
+static int threadsLeft;
+static void testPerformanceMT ()
+{
+ PRTime starttime, endtime, totaltime;
+ int entryCount = 200;
+ int threadCount = 10;
+ int entryTotal;
+ int i;
+ PRIntervalTime interval;
+
+ interval = PR_MillisecondsToInterval(100);
+ threadsLeft = threadCount * 2;
+ entryTotal = threadCount * entryCount;
+ starttime = PR_Now();
+
+ for (i = 0; i < threadCount; i++)
+ {
+ PR_CreateThread(PR_USER_THREAD, threadMain, (void*)&entryCount,
+ PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
+ PR_UNJOINABLE_THREAD, 0);
+ }
+
+ while (threadsLeft > 5)
+ DS_Sleep (interval);
+
+ endtime = PR_Now();
+
+ totaltime = (endtime - starttime) / 1000; /* ms */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Write performance:\n"
+ "entry count - %d, total time - %ld\n"
+ "latency = %d msec per entry\nthroughput = %d entry/sec\n",
+ entryCount, totaltime,
+ totaltime / entryTotal, entryTotal * 1000 / totaltime);
+
+
+ starttime = endtime;
+
+ while (threadsLeft != 0)
+ DS_Sleep (interval);
+
+ endtime = PR_Now();
+
+ totaltime = (endtime - starttime) / 1000; /* ms */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Read performance:\n"
+ "entry count - %d, total time - %ld\n"
+ "latency = %d msec per entry\nthroughput = %d entry/sec\n",
+ entryCount, totaltime,
+ totaltime / entryTotal, entryTotal * 1000 / totaltime);
+}
+
+static void testLDIF ()
+{
+ char *clDir = cl5GetDir ();
+ int rc;
+ char *baseDir;
+ char ldifFile [MAXPATHLEN + 1];
+
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Starting LDIF test ...\n");
+
+ baseDir = getBaseDir (clDir);
+ sprintf (ldifFile, "%s/cl5.ldif", baseDir);
+ slapi_ch_free ((void**)&baseDir);
+ rc = populateChangelog (ENTRY_COUNT, NULL);
+
+ if (rc == CL5_SUCCESS)
+ {
+ rc = cl5ExportLDIF (ldifFile, NULL);
+ if (rc == CL5_SUCCESS)
+ {
+ cl5Close();
+ rc = cl5ImportLDIF (clDir, ldifFile, NULL);
+ if (rc == CL5_SUCCESS)
+ cl5Open(clDir, NULL);
+ }
+ }
+
+ PR_Delete (ldifFile);
+
+ if (rc == CL5_SUCCESS)
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "LDIF test completed successfully\n");
+ else
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "LDIF test failed\n");
+}
+
+static void testAll ()
+{
+ testBasic ();
+
+ testIteration ();
+
+ testBackupRestore ();
+
+ testLDIF ();
+
+ /* testTrimming ();*/
+
+#if 0
+ /* xxxPINAKI */
+ /* these tests are not working correctly...the call to db->put() */
+ /* just hangs forever */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "Starting single threaded performance measurement ...\n");
+ testPerformance ();
+
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "Starting multi threaded performance measurement ...\n");
+ testPerformanceMT ();
+#endif
+
+}
+
+static int populateChangelog (int entryCount, CSN ***csnList)
+{
+ CSN *csn;
+ int i;
+ slapi_operation_parameters op;
+ int rc;
+ char *uniqueid;
+
+ if (csnList)
+ {
+ (*csnList) = (CSN**)slapi_ch_calloc (entryCount, sizeof (CSN*));
+ }
+
+ /* generate entries */
+ for (i = 0; i < entryCount; i++)
+ {
+ /* ONREPL need to get replica object
+ rc = csnGetNewCSNForRepl (&csn);
+ if (rc != CL5_SUCCESS) */
+ return -1;
+
+ if (csnList)
+ (*csnList) [i] = csn_dup (csn);
+ memset (&op, 0, sizeof (op));
+ op.csn = csn;
+ slapi_uniqueIDGenerateString(&uniqueid);
+ op.target_address.uniqueid = uniqueid;
+ op.target_address.dn = slapi_ch_strdup ("cn=entry,dc=example,dc=com");
+ if (i % 5 == 0)
+ {
+ op.operation_type = SLAPI_OPERATION_MODRDN;
+ op.p.p_modrdn.modrdn_deloldrdn = 1;
+ op.p.p_modrdn.modrdn_newrdn = slapi_ch_strdup("cn=entry2,dc=example,dc=com");
+ op.p.p_modrdn.modrdn_newsuperior_address.dn = NULL;
+ op.p.p_modrdn.modrdn_newsuperior_address.uniqueid = NULL;
+ op.p.p_modrdn.modrdn_mods = buildMods ();
+ }
+ else if (i % 4 == 0)
+ {
+ op.operation_type = SLAPI_OPERATION_DELETE;
+ }
+ else if (i % 3 == 0)
+ {
+
+ op.operation_type = SLAPI_OPERATION_ADD;
+ op.p.p_add.target_entry = slapi_entry_alloc ();
+ slapi_entry_set_dn (op.p.p_add.target_entry, slapi_ch_strdup(op.target_address.dn));
+ slapi_entry_set_uniqueid (op.p.p_add.target_entry, slapi_ch_strdup(op.target_address.uniqueid));
+ slapi_entry_attr_set_charptr(op.p.p_add.target_entry, "objectclass", "top");
+ slapi_entry_attr_set_charptr(op.p.p_add.target_entry, "cn", "entry");
+ }
+ else
+ {
+ op.operation_type = SLAPI_OPERATION_MODIFY;
+ op.p.p_modify.modify_mods = buildMods ();
+ }
+
+ /* ONREPL rc = cl5WriteOperation (&op, 1);*/
+ operation_parameters_done (&op);
+
+ if (rc != CL5_SUCCESS)
+ return -1;
+ }
+
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "Successfully populated changelog with %d entries\n", entryCount);
+ return 0;
+}
+
+static int processEntries (int entryCount, CSN **csnList)
+{
+ int i;
+ int rc = 0;
+ slapi_operation_parameters op;
+
+ for (i = 0; i < entryCount; i++)
+ {
+ memset (&op, 0, sizeof (op));
+
+ op.csn = csn_dup (csnList [i]);
+
+ /* rc = cl5GetOperation (&op);*/
+ if (rc != CL5_SUCCESS)
+ return -1;
+
+ operation_parameters_done (&op);
+ }
+
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
+ "Successfully read %d entries from the changelog\n", entryCount);
+ return 0;
+}
+
+void clearCSNList (CSN ***csnList, int count)
+{
+ int i;
+
+ for (i = 0; i < count; i++)
+ {
+ csn_free (&((*csnList)[i]));
+ }
+
+ slapi_ch_free ((void**)csnList);
+}
+
+static void threadMain (void *data)
+{
+ int entryCount = *(int*)data;
+ CSN **csnList;
+
+ populateChangelog (entryCount, &csnList);
+ PR_AtomicDecrement (&threadsLeft);
+
+ processEntries (entryCount, csnList);
+ PR_AtomicDecrement (&threadsLeft);
+
+ clearCSNList (&csnList, entryCount);
+}
+
+static char* getBaseDir (const char *dir)
+{
+ char *baseDir = slapi_ch_strdup (dir);
+ char *ch;
+
+ ch = &(baseDir [strlen (dir) - 2]);
+
+ while (ch >= baseDir && *ch != '\\' && *ch != '/')
+ ch --;
+
+ if (ch >= baseDir)
+ {
+ *ch = '\0';
+ }
+
+ return baseDir;
+}
+
+static LDAPMod **buildMods ()
+{
+ Slapi_Mods smods;
+ Slapi_Mod smod;
+ LDAPMod **mods;
+ struct berval bv;
+ int j, k;
+
+ slapi_mods_init (&smods, MOD_COUNT);
+
+ for (j = 0; j < MOD_COUNT; j++)
+ {
+ slapi_mod_init (&smod, VALUE_COUNT);
+ slapi_mod_set_operation (&smod, LDAP_MOD_ADD | LDAP_MOD_BVALUES);
+ slapi_mod_set_type (&smod, "attr");
+
+ for (k = 0; k < VALUE_COUNT; k++)
+ {
+ bv.bv_val = "bvalue";
+ bv.bv_len = strlen (bv.bv_val) + 1;
+ slapi_mod_add_value (&smod, &bv);
+ }
+
+ slapi_mods_add_smod (&smods, &smod);
+ /* ONREPL slapi_mod_done (&smod); */
+ }
+
+ mods = slapi_mods_get_ldapmods_passout (&smods);
+ slapi_mods_done (&smods);
+ return mods;
+}
+
+/* Format:
+ dn: cn=changelog5,cn=config
+ objectclass: top
+ objectclass: extensibleObject
+ cn: changelog5
+ nsslapd-changelogDir: d:/netscape/server4/slapd-elf/cl5 */
+static int configureChangelog ()
+{
+ Slapi_PBlock *pb = slapi_pblock_new ();
+ Slapi_Entry *e = slapi_entry_alloc ();
+ int rc;
+ char *attrs[] = {INSTANCE_ATTR, NULL};
+ Slapi_Entry **entries;
+ char cl_dir [256];
+ char *str = NULL;
+
+ /* set changelog dn */
+ slapi_entry_set_dn (e, slapi_ch_strdup (CL_DN));
+
+ /* set object classes */
+ slapi_entry_add_string(e, "objectclass", "top");
+ slapi_entry_add_string(e, "objectclass", "extensibleObject");
+
+ /* get directory instance dir */
+ slapi_search_internal_set_pb (pb, "cn=config", LDAP_SCOPE_BASE, "objectclass=*",
+ attrs, 0, NULL, NULL,
+ repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0);
+ slapi_search_internal_pb (pb);
+
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
+ if (rc != LDAP_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "failed to get server instance "
+ "directory; LDAP error - %d\n", rc);
+ rc = -1;
+ goto done;
+ }
+
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries);
+ str = slapi_entry_attr_get_charptr(entries[0], INSTANCE_ATTR);
+ sprintf (cl_dir, "%s/%s", str, "cl5db");
+ slapi_ch_free((void **)&str);
+ slapi_entry_add_string (e, CONFIG_CHANGELOG_DIR_ATTRIBUTE, cl_dir);
+
+ slapi_free_search_results_internal(pb);
+ slapi_pblock_destroy (pb);
+
+ pb = slapi_pblock_new ();
+
+ slapi_add_entry_internal_set_pb (pb, e, NULL,
+ repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0);
+
+ slapi_add_internal_pb (pb);
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
+ if (rc != LDAP_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "failed to add changelog "
+ "configuration entry; LDAP error - %d\n", rc);
+ rc = -1;
+ }
+ else
+ rc = 0;
+
+done:
+ slapi_pblock_destroy (pb);
+
+ return rc;
+}
+
+/* Format:
+ dn: cn=replica,cn="o=NetscapeRoot",cn= mapping tree,cn=config
+ objectclass: top
+ objectclass: nsds5Replica
+ objectclass: extensibleObject
+ nsds5ReplicaRoot: o=NetscapeRoot
+ nsds5ReplicaId: 2
+ nsds5flags: 1
+ cn: replica
+ */
+static int configureReplica ()
+{
+ Slapi_PBlock *pb = slapi_pblock_new ();
+ Slapi_Entry *e = slapi_entry_alloc ();
+ int rc;
+ char dn [128];
+
+ /* set changelog dn */
+ sprintf (dn, "%s,cn=\"%s\",%s", REPLICA_RDN, REPLICA_ROOT,
+ slapi_get_mapping_tree_config_root ());
+ slapi_entry_set_dn (e, slapi_ch_strdup (dn));
+
+ /* set object classes */
+ slapi_entry_add_string(e, "objectclass", "top");
+ slapi_entry_add_string(e, "objectclass", REPLICA_OC);
+ slapi_entry_add_string(e, "objectclass", "extensibleObject");
+
+ /* set other attributes */
+ slapi_entry_add_string (e, attr_replicaRoot, REPLICA_ROOT);
+ slapi_entry_add_string (e, attr_replicaId, "1");
+ slapi_entry_add_string (e, attr_flags, "1");
+
+ slapi_add_entry_internal_set_pb (pb, e, NULL,
+ repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0);
+
+ slapi_add_internal_pb (pb);
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
+ if (rc != LDAP_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "failed to add replica for (%s) "
+ "configuration entry; LDAP error - %d\n", REPLICA_ROOT, rc);
+ rc = -1;
+ }
+ else
+ rc = 0;
+
+ slapi_pblock_destroy (pb);
+
+ return rc;
+}
+
+/* generates one of each ldap operations */
+static int populateChangelogOp ()
+{
+ Slapi_PBlock *pb = slapi_pblock_new ();
+ Slapi_Entry *e = slapi_entry_alloc ();
+ int rc;
+ char dn [128], newrdn [64];
+ LDAPMod *mods[2];
+ Slapi_Mod smod;
+ struct berval bv;
+ time_t cur_time;
+
+ /* add entry */
+ cur_time = time(NULL);
+ sprintf (dn, "cn=%s,%s", ctime(&cur_time), REPLICA_ROOT);
+ slapi_entry_set_dn (e, slapi_ch_strdup (dn));
+ slapi_entry_add_string(e, "objectclass", "top");
+ slapi_entry_add_string(e, "objectclass", "extensibleObject");
+ slapi_entry_add_string (e, "mail", "jsmith@netscape.com");
+
+ slapi_add_entry_internal_set_pb (pb, e, NULL,
+ repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0);
+ slapi_add_internal_pb (pb);
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
+ slapi_pblock_destroy (pb);
+ if (rc != LDAP_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "failed to add entry (%s); "
+ "LDAP error - %d\n", dn, rc);
+ return -1;
+ }
+
+ /* modify entry */
+ pb = slapi_pblock_new ();
+ slapi_mod_init (&smod, 1);
+ slapi_mod_set_type (&smod, "mail");
+ slapi_mod_set_operation (&smod, LDAP_MOD_REPLACE | LDAP_MOD_BVALUES);
+ bv.bv_val = "jsmith@aol.com";
+ bv.bv_len = strlen (bv.bv_val);
+ slapi_mod_add_value(&smod, &bv);
+ mods[0] = (LDAPMod*)slapi_mod_get_ldapmod_byref(&smod);
+ mods[1] = NULL;
+ slapi_modify_internal_set_pb (pb, dn, mods, NULL, NULL,
+ repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0);
+ slapi_modify_internal_pb (pb);
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
+ slapi_mod_done (&smod);
+ slapi_pblock_destroy (pb);
+ if (rc != LDAP_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "failed to modify entry (%s); "
+ "LDAP error - %d\n", dn, rc);
+ return -1;
+ }
+
+ /* rename entry */
+ pb = slapi_pblock_new ();
+ cur_time = time (NULL);
+ sprintf (newrdn, "cn=renamed%s", ctime(&cur_time));
+ slapi_rename_internal_set_pb (pb, dn, newrdn, NULL, 1, NULL, NULL,
+ repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0);
+ slapi_modrdn_internal_pb (pb);
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
+ slapi_pblock_destroy (pb);
+ if (rc != LDAP_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "failed to rename entry (%s); "
+ "LDAP error - %d\n", dn, rc);
+ return -1;
+ }
+
+ /* delete the entry */
+ pb = slapi_pblock_new ();
+ sprintf (dn, "%s,%s", newrdn, REPLICA_ROOT);
+ slapi_delete_internal_set_pb (pb, dn, NULL, NULL,
+ repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0);
+ slapi_delete_internal_pb (pb);
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
+ slapi_pblock_destroy (pb);
+ if (rc != LDAP_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "failed to delete entry (%s); "
+ "LDAP error - %d\n", dn, rc);
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/ldap/servers/plugins/replication/cl5_test.h b/ldap/servers/plugins/replication/cl5_test.h
new file mode 100644
index 00000000..57d8435c
--- /dev/null
+++ b/ldap/servers/plugins/replication/cl5_test.h
@@ -0,0 +1,21 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* cl5_test.h - changelog test cases */
+
+typedef enum
+{
+ TEST_BASIC, /* open-close-delete, read-write-delete */
+ TEST_BACKUP_RESTORE,/* test backup and recovery */
+ TEST_ITERATION, /* similar to iteration used by replica upsate protocol */
+ TEST_TRIMMING, /* test changelog trimming */
+ TEST_PERFORMANCE, /* test read/write performance */
+ TEST_PERFORMANCE_MT,/* test multithreaded performance */
+ TEST_LDIF, /* test cl2ldif and ldif2cl */
+ TEST_ALL /* collective test */
+} TestType;
+
+void testChangelog (TestType type);
+
diff --git a/ldap/servers/plugins/replication/csnpl.c b/ldap/servers/plugins/replication/csnpl.c
new file mode 100644
index 00000000..7180af67
--- /dev/null
+++ b/ldap/servers/plugins/replication/csnpl.c
@@ -0,0 +1,328 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#include "csnpl.h"
+#include "llist.h"
+#include "repl_shared.h"
+
+struct csnpl
+{
+ LList* csnList; /* pending list */
+ PRRWLock* csnLock; /* lock to serialize access to PL */
+};
+
+typedef struct _csnpldata
+{
+ PRBool committed; /* True if CSN committed */
+ CSN *csn; /* The actual CSN */
+} csnpldata;
+
+/* forward declarations */
+#ifdef DEBUG
+static void _csnplDumpContentNoLock(CSNPL *csnpl, const char *caller);
+#endif
+
+CSNPL* csnplNew ()
+{
+ CSNPL *csnpl;
+
+ csnpl = (CSNPL *)slapi_ch_malloc (sizeof (CSNPL));
+ if (csnpl == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "csnplNew: failed to allocate pending list\n");
+ return NULL;
+ }
+
+ csnpl->csnList = llistNew ();
+ if (csnpl->csnList == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "csnplNew: failed to allocate pending list\n");
+ slapi_ch_free ((void**)&csnpl);
+ return NULL;
+ }
+
+ /* ONREPL: do locks need different names */
+ csnpl->csnLock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "pl_lock");
+
+ if (csnpl->csnLock == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "csnplNew: failed to create lock; NSPR error - %d\n",
+ PR_GetError ());
+ slapi_ch_free ((void**)&(csnpl->csnList));
+ slapi_ch_free ((void**)&csnpl);
+ return NULL;
+ }
+
+ return csnpl;
+}
+
+
+void
+csnpldata_free(void **data)
+{
+ csnpldata **data_to_free = (csnpldata **)data;
+ if (NULL != data_to_free)
+ {
+ if (NULL != (*data_to_free)->csn)
+ {
+ csn_free(&(*data_to_free)->csn);
+ }
+ slapi_ch_free((void **)data_to_free);
+ }
+}
+
+void csnplFree (CSNPL **csnpl)
+{
+ if ((csnpl == NULL) || (*csnpl == NULL))
+ return;
+
+ /* free all remaining nodes */
+ llistDestroy (&((*csnpl)->csnList), (FNFree)csnpldata_free);
+
+ if ((*csnpl)->csnLock);
+ PR_DestroyRWLock ((*csnpl)->csnLock);
+
+ slapi_ch_free ((void**)csnpl);
+}
+
+/* This function isnerts a CSN into the pending list
+ * Returns: 0 if the csn was successfully inserted
+ * 1 if the csn has already been seen
+ * -1 for any other kind of errors
+ */
+int csnplInsert (CSNPL *csnpl, const CSN *csn)
+{
+ int rc;
+ csnpldata *csnplnode;
+ char csn_str[CSN_STRSIZE];
+
+ if (csnpl == NULL || csn == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "csnplInsert: invalid argument\n");
+ return -1;
+ }
+
+ PR_RWLock_Wlock (csnpl->csnLock);
+
+ /* check to see if this csn is larger than the last csn in the
+ pending list. It has to be if we have not seen it since
+ the csns are always added in the accending order. */
+ csnplnode = llistGetTail (csnpl->csnList);
+ if (csnplnode && csn_compare (csnplnode->csn, csn) >= 0)
+ {
+ PR_RWLock_Unlock (csnpl->csnLock);
+ return 1;
+ }
+
+ csnplnode = (csnpldata *)slapi_ch_malloc(sizeof(csnpldata));
+ csnplnode->committed = PR_FALSE;
+ csnplnode->csn = csn_dup(csn);
+ csn_as_string(csn, PR_FALSE, csn_str);
+ rc = llistInsertTail (csnpl->csnList, csn_str, csnplnode);
+
+#ifdef DEBUG
+ _csnplDumpContentNoLock(csnpl, "csnplInsert");
+#endif
+
+ PR_RWLock_Unlock (csnpl->csnLock);
+ if (rc != 0)
+ {
+ char s[CSN_STRSIZE];
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "csnplInsert: failed to insert csn (%s) into pending list\n", csn_as_string(csn,PR_FALSE,s));
+ return -1;
+ }
+
+ return 0;
+}
+
+int csnplRemove (CSNPL *csnpl, const CSN *csn)
+{
+ csnpldata *data;
+ char csn_str[CSN_STRSIZE];
+
+ if (csnpl == NULL || csn == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "csnplRemove: invalid argument\n");
+ return -1;
+ }
+
+ csn_as_string(csn, PR_FALSE, csn_str);
+ PR_RWLock_Wlock (csnpl->csnLock);
+
+ data = (csnpldata *)llistRemove (csnpl->csnList, csn_str);
+ if (data == NULL)
+ {
+ PR_RWLock_Unlock (csnpl->csnLock);
+ return -1;
+ }
+
+#ifdef DEBUG
+ _csnplDumpContentNoLock(csnpl, "csnplRemove");
+#endif
+
+ csn_free(&data->csn);
+ slapi_ch_free((void **)&data);
+
+ PR_RWLock_Unlock (csnpl->csnLock);
+
+ return 0;
+}
+
+int csnplCommit (CSNPL *csnpl, const CSN *csn)
+{
+ csnpldata *data;
+ char csn_str[CSN_STRSIZE];
+
+ if (csnpl == NULL || csn == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "csnplCommit: invalid argument\n");
+ return -1;
+ }
+ csn_as_string(csn, PR_FALSE, csn_str);
+
+ PR_RWLock_Wlock (csnpl->csnLock);
+
+#ifdef DEBUG
+ _csnplDumpContentNoLock(csnpl, "csnplCommit");
+#endif
+
+ data = (csnpldata*)llistGet (csnpl->csnList, csn_str);
+ if (data == NULL)
+ {
+ /*
+ * In the scenario "4.x master -> 6.x legacy-consumer -> 6.x consumer"
+ * csn will have rid=65535. Hence 6.x consumer will get here trying
+ * to commit r->min_csn_pl because its rid matches that in the csn.
+ * However, r->min_csn_pl is always empty for a dedicated consumer.
+ * Exclude READ-ONLY replica ID here from error logging.
+ */
+ ReplicaId rid = csn_get_replicaid (csn);
+ if (rid < MAX_REPLICA_ID)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "csnplCommit: can't find csn %s\n", csn_str);
+ }
+ PR_RWLock_Unlock (csnpl->csnLock);
+ return -1;
+ }
+ else
+ {
+ data->committed = PR_TRUE;
+ }
+
+ PR_RWLock_Unlock (csnpl->csnLock);
+
+ return 0;
+}
+
+
+
+CSN* csnplGetMinCSN (CSNPL *csnpl, PRBool *committed)
+{
+ csnpldata *data;
+ CSN *csn = NULL;
+ PR_RWLock_Rlock (csnpl->csnLock);
+ if ((data = (csnpldata*)llistGetHead (csnpl->csnList)) != NULL)
+ {
+ csn = csn_dup(data->csn);
+ if (NULL != committed)
+ {
+ *committed = data->committed;
+ }
+ }
+ PR_RWLock_Unlock (csnpl->csnLock);
+
+ return csn;
+}
+
+
+/*
+ * Roll up the list of pending CSNs, removing all of the CSNs at the
+ * head of the the list that are committed and contiguous. Returns
+ * the largest committed CSN, or NULL if no contiguous block of
+ * committed CSNs appears at the beginning of the list. The caller
+ * is responsible for freeing the CSN returned.
+ */
+CSN *
+csnplRollUp(CSNPL *csnpl, CSN **first_commited)
+{
+ CSN *largest_committed_csn = NULL;
+ csnpldata *data;
+ PRBool freeit = PR_TRUE;
+
+ PR_RWLock_Wlock (csnpl->csnLock);
+ if (first_commited) {
+ /* Avoid non-initialization issues due to careless callers */
+ *first_commited = NULL;
+ }
+ data = (csnpldata *)llistGetHead(csnpl->csnList);
+ while (NULL != data && data->committed)
+ {
+ if (NULL != largest_committed_csn && freeit)
+ {
+ csn_free(&largest_committed_csn);
+ }
+ freeit = PR_TRUE;
+ largest_committed_csn = data->csn; /* Save it */
+ if (first_commited && (*first_commited == NULL)) {
+ *first_commited = data->csn;
+ freeit = PR_FALSE;
+ }
+ data = (csnpldata*)llistRemoveHead (csnpl->csnList);
+ slapi_ch_free((void **)&data);
+ data = (csnpldata *)llistGetHead(csnpl->csnList);
+ }
+
+#ifdef DEBUG
+ _csnplDumpContentNoLock(csnpl, "csnplRollUp");
+#endif
+
+ PR_RWLock_Unlock (csnpl->csnLock);
+ return largest_committed_csn;
+}
+
+#ifdef DEBUG
+/* Dump current content of the list - for debugging */
+void
+csnplDumpContent(CSNPL *csnpl, const char *caller)
+{
+ if (csnpl)
+ {
+ PR_RWLock_Rlock (csnpl->csnLock);
+ _csnplDumpContentNoLock (csnpl, caller);
+ PR_RWLock_Unlock (csnpl->csnLock);
+ }
+}
+
+/* helper function */
+static void _csnplDumpContentNoLock(CSNPL *csnpl, const char *caller)
+{
+ csnpldata *data;
+ void *iterator;
+ char csn_str[CSN_STRSIZE];
+
+ data = (csnpldata *)llistGetFirst(csnpl->csnList, &iterator);
+ if (data) {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "%s: CSN Pending list content:\n",
+ caller ? caller : "");
+ }
+ while (data)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "%s, %s\n",
+ csn_as_string(data->csn, PR_FALSE, csn_str),
+ data->committed ? "committed" : "not committed");
+ data = (csnpldata *)llistGetNext (csnpl->csnList, &iterator);
+ }
+}
+#endif
+
diff --git a/ldap/servers/plugins/replication/csnpl.h b/ldap/servers/plugins/replication/csnpl.h
new file mode 100644
index 00000000..ae1b4c85
--- /dev/null
+++ b/ldap/servers/plugins/replication/csnpl.h
@@ -0,0 +1,23 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* csnpl.h - interface for csn pending list */
+
+#ifndef CSNPL_H
+#define CSNPL_H
+
+#include "slapi-private.h"
+
+typedef struct csnpl CSNPL;
+
+CSNPL* csnplNew ();
+void csnplFree (CSNPL **csnpl);
+int csnplInsert (CSNPL *csnpl, const CSN *csn);
+int csnplRemove (CSNPL *csnpl, const CSN *csn);
+CSN* csnplGetMinCSN (CSNPL *csnpl, PRBool *committed);
+int csnplCommit (CSNPL *csnpl, const CSN *csn);
+CSN *csnplRollUp(CSNPL *csnpl, CSN ** first);
+void csnplDumpContent(CSNPL *csnpl, const char *caller);
+#endif
diff --git a/ldap/servers/plugins/replication/dllmain.c b/ldap/servers/plugins/replication/dllmain.c
new file mode 100644
index 00000000..3f17b14c
--- /dev/null
+++ b/ldap/servers/plugins/replication/dllmain.c
@@ -0,0 +1,91 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+ /*
+ * Microsoft Windows specifics for LIBREPLICATION DLL
+ */
+#include "ldap.h"
+
+
+#ifdef _WIN32
+/* Lifted from Q125688
+ * How to Port a 16-bit DLL to a Win32 DLL
+ * on the MSVC 4.0 CD
+ */
+BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved)
+{
+
+ switch (fdwReason)
+ {
+ case DLL_PROCESS_ATTACH:
+ /* Code from LibMain inserted here. Return TRUE to keep the
+ DLL loaded or return FALSE to fail loading the DLL.
+
+ You may have to modify the code in your original LibMain to
+ account for the fact that it may be called more than once.
+ You will get one DLL_PROCESS_ATTACH for each process that
+ loads the DLL. This is different from LibMain which gets
+ called only once when the DLL is loaded. The only time this
+ is critical is when you are using shared data sections.
+ If you are using shared data sections for statically
+ allocated data, you will need to be careful to initialize it
+ only once. Check your code carefully.
+
+ Certain one-time initializations may now need to be done for
+ each process that attaches. You may also not need code from
+ your original LibMain because the operating system may now
+ be doing it for you.
+ */
+ /*
+ * 16 bit code calls UnlockData()
+ * which is mapped to UnlockSegment in windows.h
+ * in 32 bit world UnlockData is not defined anywhere
+ * UnlockSegment is mapped to GlobalUnfix in winbase.h
+ * and the docs for both UnlockSegment and GlobalUnfix say
+ * ".. function is oboslete. Segments have no meaning
+ * in the 32-bit environment". So we do nothing here.
+ */
+
+ break;
+
+ case DLL_THREAD_ATTACH:
+ /* Called each time a thread is created in a process that has
+ already loaded (attached to) this DLL. Does not get called
+ for each thread that exists in the process before it loaded
+ the DLL.
+
+ Do thread-specific initialization here.
+ */
+ break;
+
+ case DLL_THREAD_DETACH:
+ /* Same as above, but called when a thread in the process
+ exits.
+
+ Do thread-specific cleanup here.
+ */
+ break;
+
+ case DLL_PROCESS_DETACH:
+ /* Code from _WEP inserted here. This code may (like the
+ LibMain) not be necessary. Check to make certain that the
+ operating system is not doing it for you.
+ */
+
+ break;
+ }
+ /* The return value is only used for DLL_PROCESS_ATTACH; all other
+ conditions are ignored. */
+ return TRUE; /* successful DLL_PROCESS_ATTACH */
+}
+#else
+int CALLBACK
+LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine )
+{
+ /*UnlockData( 0 );*/
+ return( 1 );
+}
+#endif
diff --git a/ldap/servers/plugins/replication/legacy_consumer.c b/ldap/servers/plugins/replication/legacy_consumer.c
new file mode 100644
index 00000000..8bf45ee1
--- /dev/null
+++ b/ldap/servers/plugins/replication/legacy_consumer.c
@@ -0,0 +1,707 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+/*
+ * repl_legacy_consumer.c - support for legacy replication (consumer-side)
+ *
+ * Support for legacy replication involves correctly dealing with
+ * the addition and removal of attribute types "copiedFrom" and
+ * "copyingFrom". The behavior is:
+ * 1) If a copiedFrom appears in an entry, and that entry is the root
+ * of a replicated area, then put the backend into "refer on update"
+ * mode and install a referral corresponding to the URL contained
+ * in the copiedFrom attribute. This referral overrides the mode
+ * of the replica, e.g. if it was previously an updateable replica,
+ * it now becomes read-only except for the updatedn.
+ * 2) If a copiedFrom disappears from an entry, or the entry containing
+ * the copiedFrom is removed, restore the backend to the state
+ * determined by the DS 5.0 replica configuration.
+ * 3) If a "copyingFrom" referral appears in an entry, and that entry
+ * is the root of a replicated area, then put the backend into
+ * "refer all operations" mode and install a referral corresponding
+ * to the URL contained in the copyingFrom attribute. This referral
+ * overrides the mode of the replica, e.g if it was previously an
+ * updateable replica, it now becomes read-only and refers all
+ * operations except for the updatedn.
+ * 4) If a copyingFrom disappears from an entry, or the entry containing
+ * the copyingFrom is removed, restore the backend to the state
+ * determined by the DS 5.0 replica configuration.
+ */
+
+
+#include "repl5.h"
+#include "repl.h"
+
+/* Forward Declarations */
+static int legacy_consumer_config_add (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg);
+static int legacy_consumer_config_modify (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg);
+static int legacy_consumer_config_delete (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg);
+
+static int legacy_consumer_extract_config(Slapi_Entry* entry, char *returntext);
+static int legacy_consumer_read_config ();
+static void legacy_consumer_encode_pw (Slapi_Entry *e);
+static void set_legacy_purl (Slapi_PBlock *pb, const char *purl);
+static int get_legacy_referral (Slapi_Entry *e, char **referral, char **state);
+
+#define LEGACY_CONSUMER_CONFIG_DN "cn=legacy consumer," REPL_CONFIG_TOP
+#define LEGACY_CONSUMER_FILTER "(objectclass=*)"
+
+/* Configuration parameters local to this module */
+static Slapi_DN *legacy_consumer_replicationdn = NULL;
+static char *legacy_consumer_replicationpw = NULL;
+/* Lock which protects the above config parameters */
+PRRWLock *legacy_consumer_config_lock = NULL;
+
+static PRBool
+target_is_a_replica_root(Slapi_PBlock *pb, const Slapi_DN **root)
+{
+ char *dn;
+ Slapi_DN *sdn;
+ PRBool return_value;
+ Object *repl_obj;
+
+ slapi_pblock_get(pb, SLAPI_TARGET_DN, &dn);
+ sdn = slapi_sdn_new_dn_byref(dn);
+ repl_obj = replica_get_replica_from_dn(sdn);
+ if (NULL != repl_obj)
+ {
+ Replica *r = object_get_data(repl_obj);
+ *root = replica_get_root(r);
+ return_value = PR_TRUE;
+ object_release(repl_obj);
+ }
+ else
+ {
+ *root = NULL;
+ return_value = PR_FALSE;
+ }
+ slapi_sdn_free(&sdn);
+ return return_value;
+}
+
+
+
+static int
+parse_cfstring(const char *cfstring, char **referral, char **generation, char **lastreplayed)
+{
+ int return_value = -1;
+ char *ref, *gen, *lastplayed;
+
+ if (cfstring != NULL)
+ {
+ char *tmp;
+ char *cfcopy = slapi_ch_strdup(cfstring);
+ ref = cfcopy;
+ tmp = strchr(cfcopy, ' ');
+ if (NULL != tmp)
+ {
+ *tmp++ = '\0';
+ while ('\0' != *tmp && ' ' == *tmp) tmp++;
+ gen = tmp;
+ tmp = strchr(gen, ' ');
+ if (NULL != tmp)
+ {
+ *tmp++ = '\0';
+ while ('\0' != *tmp && ' ' == *tmp) tmp++;
+ lastplayed = tmp;
+ return_value = 0;
+ }
+ }
+
+ if (return_value == 0)
+ {
+ if (referral)
+ *referral = slapi_ch_strdup(ref);
+ if (generation)
+ *generation = slapi_ch_strdup(gen);
+ if (lastreplayed)
+ *lastreplayed = slapi_ch_strdup(lastplayed);
+ }
+ slapi_ch_free((void **)&cfcopy);
+ }
+ return return_value;
+}
+
+
+
+/*
+ * This is called from the consumer post-op plugin point.
+ * It's called if:
+ * 1) The operation is an add or modify operation, and a
+ * copiedfrom/copyingfrom was found in the entry/mods, or
+ * 2) the operation is a delete operation, or
+ * 3) the operation is a moddn operation.
+ */
+
+void
+process_legacy_cf(Slapi_PBlock *pb)
+{
+ consumer_operation_extension *opext;
+ Slapi_Operation *op;
+ char *referral_array[2] = {0};
+ char *referral;
+ char *state;
+ int rc;
+ const Slapi_DN *replica_root_sdn = NULL;
+ Slapi_Entry *e;
+
+ slapi_pblock_get(pb, SLAPI_OPERATION, &op);
+ opext = (consumer_operation_extension*) repl_con_get_ext (REPL_CON_EXT_OP, op);
+
+ if (opext->has_cf)
+ {
+ PR_ASSERT (operation_get_type (op) == SLAPI_OPERATION_ADD ||
+ operation_get_type (op) == SLAPI_OPERATION_MODIFY);
+
+ if ((PR_FALSE == target_is_a_replica_root(pb, &replica_root_sdn)) ||
+ (NULL == replica_root_sdn)){
+ return;
+ }
+
+ slapi_pblock_get (pb, SLAPI_ENTRY_POST_OP, &e);
+ PR_ASSERT (e);
+
+ if (NULL == e)
+ return;
+
+ rc = get_legacy_referral (e, &referral, &state);
+ if (rc == 0)
+ {
+ referral_array[0] = referral;
+ referral_array[1] = NULL;
+ repl_set_mtn_state_and_referrals(replica_root_sdn, state, NULL, NULL,
+ referral_array);
+ /* set partial url in the replica_object */
+ set_legacy_purl (pb, referral);
+
+ slapi_ch_free((void **)&referral);
+ }
+
+ }
+}
+
+void legacy_consumer_be_state_change (void *handle, char *be_name,
+ int old_be_state, int new_be_state)
+{
+ Object *r_obj;
+ Replica *r;
+
+ /* we only interested when a backend is coming online */
+ if (new_be_state == SLAPI_BE_STATE_ON)
+ {
+ r_obj = replica_get_for_backend (be_name);
+ if (r_obj)
+ {
+ r = (Replica*)object_get_data (r_obj);
+ PR_ASSERT (r);
+
+ if (replica_is_legacy_consumer (r))
+ legacy_consumer_init_referrals (r);
+
+ object_release (r_obj);
+ }
+ }
+}
+
+
+static int
+dont_allow_that(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, int *returncode, char *returntext, void *arg)
+{
+ *returncode = LDAP_UNWILLING_TO_PERFORM;
+ return SLAPI_DSE_CALLBACK_ERROR;
+}
+
+int
+legacy_consumer_config_init()
+{
+ /* The FE DSE *must* be initialised before we get here */
+ int rc;
+
+ if ((legacy_consumer_config_lock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "legacy_consumer_config_lock")) == NULL) {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "Failed to create legacy_consumer config read-write lock\n");
+ exit(1);
+ }
+
+ rc = legacy_consumer_read_config ();
+ if (rc != LDAP_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "Failed to initialize legacy replication configuration\n");
+ return 1;
+ }
+
+ slapi_config_register_callback(SLAPI_OPERATION_ADD,DSE_FLAG_PREOP,LEGACY_CONSUMER_CONFIG_DN,LDAP_SCOPE_SUBTREE,LEGACY_CONSUMER_FILTER,legacy_consumer_config_add,NULL);
+ slapi_config_register_callback(SLAPI_OPERATION_MODIFY,DSE_FLAG_PREOP,LEGACY_CONSUMER_CONFIG_DN,LDAP_SCOPE_SUBTREE,LEGACY_CONSUMER_FILTER,legacy_consumer_config_modify,NULL);
+ slapi_config_register_callback(SLAPI_OPERATION_MODRDN,DSE_FLAG_PREOP,LEGACY_CONSUMER_CONFIG_DN,LDAP_SCOPE_SUBTREE,LEGACY_CONSUMER_FILTER,dont_allow_that,NULL);
+ slapi_config_register_callback(SLAPI_OPERATION_DELETE,DSE_FLAG_PREOP,LEGACY_CONSUMER_CONFIG_DN,LDAP_SCOPE_SUBTREE,LEGACY_CONSUMER_FILTER,legacy_consumer_config_delete,NULL);
+
+ return 0;
+}
+
+static int
+legacy_consumer_config_add (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg)
+{
+ int rc;
+
+ rc = legacy_consumer_extract_config(e, returntext);
+ if (rc != LDAP_SUCCESS)
+ {
+ *returncode = rc;
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Failed to configure legacy replication\n");
+ return SLAPI_DSE_CALLBACK_ERROR;
+ }
+ /* make sure that the password is encoded */
+ legacy_consumer_encode_pw(e);
+
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "legacy_consumer_config_add: "
+ "successfully configured legacy consumer credentials\n");
+
+ return SLAPI_DSE_CALLBACK_OK;
+}
+
+#define config_copy_strval( s ) s ? slapi_ch_strdup (s) : NULL;
+
+static int
+legacy_consumer_config_modify (Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, int *returncode, char *returntext, void *arg)
+{
+ int rc= 0;
+ LDAPMod **mods;
+ int not_allowed = 0;
+ int i;
+
+ if (returntext)
+ {
+ returntext[0] = '\0';
+ }
+ *returncode = LDAP_SUCCESS;
+
+
+ slapi_pblock_get( pb, SLAPI_MODIFY_MODS, &mods );
+ PR_RWLock_Wlock (legacy_consumer_config_lock);
+
+ for (i = 0; (mods[i] && (!not_allowed)); i++)
+ {
+ if (mods[i]->mod_op & LDAP_MOD_DELETE)
+ {
+ /* We don't support deleting an attribute from cn=config */
+ }
+ else
+ {
+ int j;
+ for (j = 0; ((mods[i]->mod_values[j]) && (LDAP_SUCCESS == rc)); j++)
+ {
+ char *config_attr, *config_attr_value;
+ int mod_type;
+ config_attr = (char *) mods[i]->mod_type;
+ config_attr_value = (char *) mods[i]->mod_bvalues[j]->bv_val;
+ /* replace existing value */
+ mod_type = mods[i]->mod_op & ~LDAP_MOD_BVALUES;
+ if ( strcasecmp (config_attr, CONFIG_LEGACY_REPLICATIONDN_ATTRIBUTE ) == 0 )
+ {
+ if (legacy_consumer_replicationdn)
+ slapi_sdn_free (&legacy_consumer_replicationdn);
+
+ if (mod_type == LDAP_MOD_REPLACE)
+ {
+ if (config_attr_value)
+ legacy_consumer_replicationdn = slapi_sdn_new_dn_byval (config_attr_value);
+ }
+ else if (mod_type == LDAP_MOD_DELETE)
+ {
+ legacy_consumer_replicationdn = NULL;
+ }
+ else if (mod_type == LDAP_MOD_ADD)
+ {
+ if (legacy_consumer_replicationdn != NULL)
+ {
+ not_allowed = 1;
+ slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name,
+ "Multiple replicationdns not permitted." );
+ }
+ else
+ {
+ if (config_attr_value)
+ legacy_consumer_replicationdn = slapi_sdn_new_dn_byval (config_attr_value);
+ }
+ }
+ }
+ else if ( strcasecmp ( config_attr, CONFIG_LEGACY_REPLICATIONPW_ATTRIBUTE ) == 0 )
+ {
+ if (mod_type == LDAP_MOD_REPLACE)
+ {
+ legacy_consumer_replicationpw = config_copy_strval(config_attr_value);
+ }
+ else if (mod_type == LDAP_MOD_DELETE)
+ {
+ legacy_consumer_replicationpw = NULL;
+ }
+ else if (mod_type == LDAP_MOD_ADD)
+ {
+ if (legacy_consumer_replicationpw != NULL)
+ {
+ not_allowed = 1;
+ slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name,
+ "Multiple replicationpws not permitted." );
+ }
+ else
+ {
+ legacy_consumer_replicationpw = config_copy_strval(config_attr_value);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ PR_RWLock_Unlock (legacy_consumer_config_lock);
+
+
+ if (not_allowed)
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name,
+ "Failed to modify legacy replication configuration\n" );
+ *returncode= LDAP_CONSTRAINT_VIOLATION;
+ return SLAPI_DSE_CALLBACK_ERROR;
+ }
+
+ /* make sure that the password is encoded */
+ legacy_consumer_encode_pw (e);
+
+ return SLAPI_DSE_CALLBACK_OK;
+}
+
+static int
+legacy_consumer_config_delete (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg)
+{
+
+ PR_RWLock_Wlock (legacy_consumer_config_lock);
+ if (legacy_consumer_replicationdn)
+ slapi_sdn_free (&legacy_consumer_replicationdn);
+ if (legacy_consumer_replicationpw)
+ slapi_ch_free ((void**)&legacy_consumer_replicationpw);
+
+ legacy_consumer_replicationdn = NULL;
+ legacy_consumer_replicationpw = NULL;
+ PR_RWLock_Unlock (legacy_consumer_config_lock);
+
+ *returncode = LDAP_SUCCESS;
+ return SLAPI_DSE_CALLBACK_OK;
+}
+
+/*
+ * Given the changelog configuration entry, extract the configuration directives.
+ */
+static int
+legacy_consumer_extract_config(Slapi_Entry* entry, char *returntext)
+{
+ int rc = LDAP_SUCCESS; /* OK */
+ char *arg;
+
+ PR_RWLock_Wlock (legacy_consumer_config_lock);
+
+ arg= slapi_entry_attr_get_charptr(entry,CONFIG_LEGACY_REPLICATIONDN_ATTRIBUTE);
+ if (arg)
+ legacy_consumer_replicationdn = slapi_sdn_new_dn_passin (arg);
+
+ arg= slapi_entry_attr_get_charptr(entry,CONFIG_LEGACY_REPLICATIONPW_ATTRIBUTE);
+ legacy_consumer_replicationpw = arg;
+
+ PR_RWLock_Unlock (legacy_consumer_config_lock);
+
+ return rc;
+}
+
+
+
+
+static int
+legacy_consumer_read_config ()
+{
+ int rc = LDAP_SUCCESS;
+ int scope= LDAP_SCOPE_BASE;
+ Slapi_PBlock *pb;
+
+ pb = slapi_pblock_new ();
+ slapi_search_internal_set_pb (pb, LEGACY_CONSUMER_CONFIG_DN, scope,
+ "(objectclass=*)", NULL /*attrs*/, 0 /* attrs only */,
+ NULL /* controls */, NULL /* uniqueid */,
+ repl_get_plugin_identity(PLUGIN_LEGACY_REPLICATION), 0 /* actions */);
+ slapi_search_internal_pb (pb);
+ slapi_pblock_get( pb, SLAPI_PLUGIN_INTOP_RESULT, &rc );
+ if ( LDAP_SUCCESS == rc )
+ {
+ Slapi_Entry **entries = NULL;
+ slapi_pblock_get( pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries );
+ if ( NULL != entries && NULL != entries[0])
+ {
+ /* Extract the config info from the changelog entry */
+ rc = legacy_consumer_extract_config(entries[0], NULL);
+ }
+ }
+ else
+ {
+ rc = LDAP_SUCCESS;
+ }
+ slapi_free_search_results_internal(pb);
+ slapi_pblock_destroy(pb);
+
+ return rc;
+}
+
+
+int
+legacy_consumer_is_replicationdn(char *dn)
+{
+ int return_value = 0; /* Assume not */
+
+ if (NULL != dn && '\0' != dn[0])
+ {
+ if (NULL != legacy_consumer_replicationdn)
+ {
+ Slapi_DN *sdn = slapi_sdn_new_dn_byref (dn);
+
+ if (slapi_sdn_compare (legacy_consumer_replicationdn, sdn) == 0) {
+ return_value = 1;
+ }
+
+ slapi_sdn_free (&sdn);
+ }
+ }
+ return return_value;
+}
+
+
+int
+legacy_consumer_is_replicationpw(struct berval *pwval)
+{
+ int return_value = 0; /* Assume not */
+
+ if (NULL != pwval && NULL != pwval->bv_val)
+ {
+ if (NULL != legacy_consumer_replicationpw &&
+ '\0' != legacy_consumer_replicationpw[0]) {
+ struct berval *pwvals[2];
+ struct berval config_pw;
+
+ config_pw.bv_val = legacy_consumer_replicationpw;
+ config_pw.bv_len = strlen(legacy_consumer_replicationpw);
+ pwvals[0] = &config_pw;
+ pwvals[1] = NULL;
+
+ return_value = slapi_pw_find(pwvals, pwval) == 0;
+ }
+ }
+ return return_value;
+}
+
+static void
+legacy_consumer_free_config ()
+{
+ if (NULL != legacy_consumer_replicationdn) {
+ slapi_sdn_free(&legacy_consumer_replicationdn);
+ }
+ if (NULL != legacy_consumer_replicationpw) {
+ slapi_ch_free((void **) &legacy_consumer_replicationpw);
+ }
+}
+
+
+
+static void
+legacy_consumer_encode_pw (Slapi_Entry *e)
+{
+ char *updatepw = slapi_entry_attr_get_charptr(e,
+ CONFIG_LEGACY_REPLICATIONPW_ATTRIBUTE);
+ int is_encoded;
+ char *encoded_value = NULL;
+
+ if (updatepw != NULL)
+ {
+ is_encoded = slapi_is_encoded (updatepw);
+
+ if (!is_encoded)
+ {
+ encoded_value = slapi_encode (updatepw, "SHA");
+ }
+
+ if (encoded_value)
+ {
+ slapi_entry_attr_set_charptr(e,
+ CONFIG_LEGACY_REPLICATIONPW_ATTRIBUTE, encoded_value);
+ }
+ }
+}
+
+static void
+set_legacy_purl (Slapi_PBlock *pb, const char *purl)
+{
+ Object *r_obj;
+ Replica *r;
+
+ r_obj = replica_get_replica_for_op (pb);
+ PR_ASSERT (r_obj);
+ r = (Replica*)object_get_data (r_obj);
+ PR_ASSERT (r && replica_is_legacy_consumer(r));
+
+ replica_set_legacy_purl (r, purl);
+
+ object_release (r_obj);
+}
+
+/* this function get referrals from an entry.
+ Returns 0 if successful
+ 1 if no referrals are present
+ -1 in case of error
+ */
+static int
+get_legacy_referral (Slapi_Entry *e, char **referral, char **state)
+{
+ char* pat = "ldap://%s";
+ const char *val = NULL;
+ char *hostport;
+ int rc = 1;
+ Slapi_Attr *attr;
+ const Slapi_Value *sval;
+
+ PR_ASSERT (e && referral && state);
+
+ /* Find any copiedFrom/copyingFrom attributes -
+ copyingFrom has priority */
+ if (slapi_entry_attr_find(e, type_copyingFrom, &attr) == 0)
+ {
+ slapi_attr_first_value(attr, (Slapi_Value **)&sval);
+ val = slapi_value_get_string(sval);
+ *state = STATE_REFERRAL;
+ }
+ else if (slapi_entry_attr_find(e, type_copiedFrom, &attr) == 0)
+ {
+ slapi_attr_first_value(attr, (Slapi_Value **)&sval);
+ val = slapi_value_get_string(sval);
+ *state = STATE_UPDATE_REFERRAL;
+ }
+
+ if (val)
+ {
+ rc = parse_cfstring(val, &hostport, NULL, NULL);
+ if (rc != 0)
+ {
+ const char *target_dn = slapi_entry_get_dn_const(e);
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Warning: a copiedFrom "
+ "or copyingFrom attribute was added to or removed from an "
+ "entry that is not the root of a replicated area. It is possible "
+ "that a legacy replication supplier is incorrectly configured "
+ "to supply updates to the subtree rooted at %s\n",
+ target_dn == NULL ? "null" : target_dn);
+ }
+ else
+ {
+ *referral = slapi_ch_malloc (strlen (pat) + strlen (hostport));
+
+ sprintf (*referral, pat, hostport);
+
+ slapi_ch_free ((void**)&hostport);
+ }
+ }
+ else
+ {
+ rc = 1; /* no copiedFrom or copyingFrom int the entry */
+ }
+
+ return rc;
+}
+
+/* this function is called during server startup or when replica's data
+ is reloaded. It sets up referrals in the mapping tree based on the
+ copiedFrom and copyingFrom attributes. It also sets up partial url in
+ the replica object used to update RUV.
+ Returns 0 if successful and -1 otherwise
+
+ */
+int
+legacy_consumer_init_referrals (Replica *r)
+{
+ Slapi_PBlock *pb;
+ const Slapi_DN *root_sdn;
+ const char *root_dn;
+ char *attrs[] = {"copiedFrom", "copyingFrom"};
+ int rc;
+ Slapi_Entry **entries = NULL;
+ char *referral = NULL;
+ char *referral_array[2];
+ char *state = NULL;
+
+ PR_ASSERT (r);
+
+ pb = slapi_pblock_new ();
+ PR_ASSERT (pb);
+
+ root_sdn = replica_get_root(r);
+ PR_ASSERT (root_sdn);
+
+ root_dn = slapi_sdn_get_ndn(root_sdn);
+ PR_ASSERT (root_dn);
+
+ slapi_search_internal_set_pb (pb, root_dn, LDAP_SCOPE_BASE, "objectclass=*",attrs,
+ 0 /* attrsonly */, NULL /* controls */,
+ NULL /* uniqueid */,
+ repl_get_plugin_identity (PLUGIN_LEGACY_REPLICATION),
+ 0 /* flags */);
+
+ slapi_search_internal_pb (pb);
+
+ slapi_pblock_get (pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
+ if (rc != LDAP_SUCCESS)
+ {
+ if (rc == LDAP_REFERRAL)
+ {
+ /* We are in referral mode, probably because ORC failed */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "legacy_consumer_init_referrals "
+ "data for replica %s is in referral mode due to failed "
+ "initialization. Replica need to be reinitialized\n",
+ root_dn);
+ rc = 0;
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "legacy_consumer_init_referrals "
+ "failed to obtain root entry for replica %s; LDAP error - %d\n",
+ root_dn, rc);
+ rc = -1;
+ }
+
+ goto done;
+ }
+
+ slapi_pblock_get (pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries);
+
+ PR_ASSERT (entries && entries[0]);
+
+ rc = get_legacy_referral (entries[0], &referral, &state);
+ if (rc == 0)
+ {
+ referral_array[0] = referral;
+ referral_array[1] = NULL;
+ repl_set_mtn_state_and_referrals(root_sdn, state, NULL, NULL, referral_array);
+
+ /* set purtial url in the replica_object */
+ replica_set_legacy_purl (r, referral);
+
+ slapi_ch_free((void **)&referral);
+ }
+ else if (rc == 1) /* no referrals - treat as success */
+ {
+ rc = 0;
+ }
+
+ slapi_free_search_results_internal (pb);
+
+done:
+
+ slapi_pblock_destroy (pb);
+ return rc;
+}
+
diff --git a/ldap/servers/plugins/replication/llist.c b/ldap/servers/plugins/replication/llist.c
new file mode 100644
index 00000000..175ea48f
--- /dev/null
+++ b/ldap/servers/plugins/replication/llist.c
@@ -0,0 +1,336 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* llist.c - single link list implementation */
+
+#include <string.h>
+#include "slapi-plugin.h"
+#include "slapi-private.h"
+#include "llist.h"
+#include "repl_shared.h"
+
+/* data structures */
+
+/* link list node */
+typedef struct lnode
+{
+ char *key;
+ void *data;
+ struct lnode *next;
+} LNode;
+
+/* This structure defines a one-way linked list with head and tail pointers.
+ The list contains a "dummy" head node which makes sure that every node
+ has a previous node. This allows to remove a node during iteration without
+ breaking the list */
+struct llist
+{
+ LNode *head;
+ LNode *tail;
+};
+
+/* forward declarations */
+static LNode* _llistNewNode (const char *key, void *data);
+static void _llistDestroyNode (LNode **node, FNFree fnFree);
+
+LList* llistNew ()
+{
+ LList *list = (LList*) slapi_ch_calloc (1, sizeof (LList));
+
+ /* allocate a special head node - it contains no data but just
+ fulfills the requirement that every node has a previous one.
+ This is used during iteration with removal */
+ if (list)
+ {
+ list->head = (LNode*)slapi_ch_calloc (1, sizeof (LNode));
+ if (list->head == NULL)
+ {
+ slapi_ch_free ((void**)&list);
+ }
+ }
+
+ return list;
+}
+
+void llistDestroy (LList **list, FNFree fnFree)
+{
+ LNode *node = NULL, *prev_node;
+
+ if (list == NULL || *list == NULL)
+ return;
+
+ if ((*list)->head)
+ node = (*list)->head->next;
+
+ while (node)
+ {
+ prev_node = node;
+ node = node->next;
+ _llistDestroyNode (&prev_node, fnFree);
+ }
+
+ slapi_ch_free ((void**)&((*list)->head));
+ slapi_ch_free ((void**)list);
+}
+
+void* llistGetFirst(LList *list, void **iterator)
+{
+ if (list == NULL || iterator == NULL || list->head == NULL || list->head->next == NULL)
+ {
+ /* empty list or error */
+ return NULL;
+ }
+
+ /* Iterator points to the previous element (so that we can remove current element
+ and still keep the list in tact. In case of the first element, iterator points
+ to the dummy head element */
+ (*iterator) = list->head;
+ return list->head->next->data;
+}
+
+void* llistGetNext (LList *list, void **iterator)
+{
+ LNode *node;
+
+ if (list == NULL || list->head == NULL || iterator == NULL || *iterator == NULL)
+ {
+ /* end of the list or error */
+ return NULL;
+ }
+
+ /* Iterator points to the previous element (so that we can
+ remove current element and still keep list in tact. */
+ node = *(LNode **)iterator;
+ node = node->next;
+
+ (*iterator) = node;
+
+ if (node && node->next)
+ return node->next->data;
+ else
+ return NULL;
+}
+
+void* llistRemoveCurrentAndGetNext (LList *list, void **iterator)
+{
+ LNode *prevNode, *node;
+
+ /* end of the list is reached or error occured */
+ if (list == NULL || iterator == NULL || *iterator == NULL)
+ return NULL;
+
+ /* Iterator points to the previous element (so that we can
+ remove current element and still keep list in tact. */
+ prevNode = *(LNode **)iterator;
+ node = prevNode->next;
+ if (node)
+ {
+ prevNode->next = node->next;
+ _llistDestroyNode (&node, NULL);
+ node = prevNode->next;
+ if (node)
+ return node->data;
+ else
+ return NULL;
+ }
+ else
+ return NULL;
+}
+
+void* llistGetHead (LList *list)
+{
+ if (list == NULL || list->head == NULL || list->head->next == NULL)
+ {
+ /* empty list or error */
+ return NULL;
+ }
+
+ return list->head->next->data;
+}
+
+void* llistGetTail (LList *list)
+{
+ if (list == NULL || list->tail == NULL)
+ {
+ /* empty list or error */
+ return NULL;
+ }
+
+ return list->tail->data;
+}
+
+void* llistGet (LList *list, const char* key)
+{
+ LNode *node;
+
+ /* empty list or invalid input */
+ if (list == NULL || list->head == NULL || list->head->next == NULL || key == NULL)
+ return NULL;
+
+ node = list->head->next;
+ while (node)
+ {
+ if (node->key && strcmp (key, node->key) == 0)
+ {
+ return node->data;
+ }
+
+ node = node->next;
+ }
+
+ /* node with specified key is not found */
+ return NULL;
+}
+
+int llistInsertHead (LList *list, const char *key, void *data)
+{
+ LNode *node;
+ if (list == NULL || list->head == NULL || data == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name, "llistInsertHead: invalid argument\n");
+ return -1;
+ }
+
+ node = _llistNewNode (key, data);
+ if (node == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name, "llistInsertHead: failed to allocate list node\n");
+ return -1;
+ }
+
+ if (list->head->next == NULL) /* empty list */
+ {
+ list->head->next = node;
+ list->tail = node;
+ }
+ else
+ {
+ node->next = list->head->next;
+ list->head->next = node;
+ }
+
+ return 0;
+}
+
+int llistInsertTail (LList *list, const char *key, void *data)
+{
+ LNode *node;
+ if (list == NULL || list->head == NULL || data == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name, "llistInsertHead: invalid argument\n");
+ return -1;
+ }
+
+ node = _llistNewNode (key, data);
+ if (node == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name, "llistInsertHead: failed to allocate list node\n");
+ return -1;
+ }
+
+ if (list->head->next == NULL) /* empty list */
+ {
+ list->head->next = node;
+ list->tail = node;
+ }
+ else
+ {
+ list->tail->next = node;
+ list->tail = node;
+ }
+
+ return 0;
+}
+
+void* llistRemoveHead (LList *list)
+{
+ LNode *node;
+ void *data;
+
+ if (list == NULL || list->head == NULL || list->head->next == NULL)
+ return NULL;
+
+ node = list->head->next;
+ data = node->data;
+
+ list->head->next = node->next;
+
+ /* last element removed */
+ if (list->head->next == NULL)
+ list->tail = NULL;
+
+ _llistDestroyNode (&node, NULL);
+
+ return data;
+}
+
+void* llistRemove (LList *list, const char *key)
+{
+ LNode *node, *prev_node;
+ void *data;
+
+ if (list == NULL || list->head == NULL || list->head->next == NULL || key == NULL)
+ return NULL;
+
+ node = list->head->next;
+ prev_node = list->head;
+ while (node)
+ {
+ if (node->key && strcmp (key, node->key) == 0)
+ {
+ prev_node->next = node->next;
+ /* last element removed */
+ if (node->next == NULL)
+ {
+ /* no more elements in the list */
+ if (list->head->next == NULL)
+ {
+ list->tail = NULL;
+ }
+ else
+ {
+ list->tail = prev_node;
+ }
+ }
+
+ data = node->data;
+ _llistDestroyNode (&node, NULL);
+ return data;
+ }
+
+ prev_node = node;
+ node = node->next;
+ }
+
+ /* node with specified key is not found */
+ return NULL;
+}
+
+static LNode* _llistNewNode (const char *key, void *data)
+{
+ LNode *node = (LNode*) slapi_ch_malloc (sizeof (LNode));
+ if (node == NULL)
+ return NULL;
+
+ if (key)
+ node->key = slapi_ch_strdup (key);
+ else
+ node->key = NULL;
+
+ node->data = data;
+ node->next = NULL;
+
+ return node;
+}
+
+static void _llistDestroyNode (LNode **node, FNFree fnFree)
+{
+ if ((*node)->data && fnFree)
+ fnFree (&(*node)->data);
+ if ((*node)->key)
+ slapi_ch_free ((void**)&((*node)->key));
+
+ slapi_ch_free ((void**)node);
+}
diff --git a/ldap/servers/plugins/replication/llist.h b/ldap/servers/plugins/replication/llist.h
new file mode 100644
index 00000000..3b196ef8
--- /dev/null
+++ b/ldap/servers/plugins/replication/llist.h
@@ -0,0 +1,26 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* llist.h - single link list interface */
+
+#ifndef LLIST_H
+#define LLIST_H
+typedef struct llist LList;
+
+LList* llistNew ();
+void llistDestroy (LList **list, FNFree fnFree);
+void* llistGetFirst(LList *list, void **iterator);
+void* llistGetNext (LList *list, void **iterator);
+void* llistRemoveCurrentAndGetNext (LList *list, void **iterator);
+void* llistGetHead (LList *list);
+void* llistGetTail (LList *list);
+void* llistGet (LList *list, const char* key);
+int llistInsertHead (LList *list, const char *key, void *data);
+int llistInsertTail (LList *list, const char *key, void *data);
+void* llistRemoveHead (LList *list);
+void* llistRemove (LList *list, const char *key);
+
+#endif
+
diff --git a/ldap/servers/plugins/replication/profile.c b/ldap/servers/plugins/replication/profile.c
new file mode 100644
index 00000000..0a7de374
--- /dev/null
+++ b/ldap/servers/plugins/replication/profile.c
@@ -0,0 +1,42 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#include "slapi-plugin.h"
+#include "repl.h"
+
+
+/* module: provide an interface to the profile file */
+
+static FILE *profile_fd=NULL;
+
+/* JCMREPL - Could build up in an AVL tree and dump out to disk at the end... */
+
+void profile_log(char *file,int line)
+{
+ if (profile_fd==NULL)
+ slapi_log_error(,"profile_log: profile file not open.");
+ else
+ {
+ /* JCMREPL - Probably need a lock around here */
+ fprintf(profile_fd,"%s %d\n",file,line);
+ }
+}
+
+void profile_open()
+{
+ char filename[MAX_FILENAME];
+ strncpy(filename,CFG_rootpath,MAX_FILENAME);
+ strcat(filename,CFG_profilefile);
+ profile_fd= textfile_open(filename,"a");
+}
+
+void profile_close()
+{
+ if (profile_fd==NULL)
+ slapi_log_error(,"profile_close: profile file not open.");
+ else
+ textfile_close(profile_fd);
+}
diff --git a/ldap/servers/plugins/replication/repl.h b/ldap/servers/plugins/replication/repl.h
new file mode 100644
index 00000000..8e502816
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl.h
@@ -0,0 +1,366 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#ifndef _REPL_H_
+#define _REPL_H_
+
+#include <limits.h>
+#include <time.h>
+#include <stdio.h>
+#include <string.h>
+#ifndef _WIN32
+#include <sys/param.h>
+#endif /* _WIN32 */
+
+#include "portable.h" /* GGOODREPL - is this cheating? */
+#include "ldaplog.h"
+#include "repl_shared.h"
+#include "cl4.h"
+
+typedef struct schedule_item
+{
+ unsigned long sch_start; /* seconds after midnight */
+ unsigned long sch_length; /* sec */
+ unsigned int sch_weekdays; /* bit mask; LSB = Sunday */
+ struct schedule_item* sch_next;
+} schedule_item;
+
+/* XXXggood - copied from slap.h - bad */
+#if defined( XP_WIN32 )
+#define NO_TIME (time_t)0 /* cannot be -1, NT's localtime( -1 ) returns NULL */
+#else
+#define NO_TIME (time_t)-1 /* a value that time() does not return */
+#endif
+
+/*
+ * A status message contains a time, the textual message,
+ * and a count of the number of times the message occured.
+ */
+typedef struct _status_message {
+ time_t sm_time;
+ char *sm_message;
+ int sm_occurances;
+} status_message;
+
+/*
+ * A status_message_list is a circular array of status messages.
+ * Old messages roll off the end and are discarded.
+ */
+typedef struct _status_message_list {
+ int sml_size; /* number of slots in array */
+ int sml_tail; /* next slot to be written */
+ status_message *sml_messages; /* array of messages */
+} sm_list;
+#define NUM_REPL_MESSAGES 20 /* max # of messages to save */
+
+/* Selective attribute Inclusion states. ORDERING IS SIGNIFICANT */
+#define IMPLICITLY_INCLUDED 1
+#define IMPLICITLY_EXCLUDED 2
+#define EXPLICITLY_EXCLUDED 3
+#define EXPLICITLY_INCLUDED 4
+
+#if defined(__JCMREPL_FILTER__)
+/*
+ * Structure used to implement selective attribute filtering.
+ * sa_filter nodes are arranged in a linked list.
+ */
+typedef struct _sa_filter {
+ Slapi_Filter *sa_filter; /* Filter to apply */
+ int sa_isexclude; /* non-zero if list is exclude list */
+ char **sa_attrlist; /* array - attrs to replicate */
+ struct _sa_filter *sa_next; /* Link to next struct */
+} sa_filter;
+#endif
+
+typedef unsigned long changeNumber;
+#define a2changeNumber( a ) strtoul(( a ), (char **)NULL, 10 )
+
+#define AUTH_SIMPLE 1
+#define AUTH_KERBEROS 2
+
+typedef struct modinfo {
+ char *type;
+ char *value;
+ int len;
+} modinfo;
+
+/*
+ * Representation of one change entry from the replog file.
+ */
+typedef struct repl {
+ char *time; /* time of modification */
+ changeNumber change; /* number of this change */
+ char *dn; /* dn of entry being modified - normalized */
+ char *raw_dn; /* dn of entry - not normalized */
+ int changetype; /* type of change */
+ modinfo *mods; /* modifications to make */
+ char *newrdn; /* new rdn for modrdn */
+ int deleteoldrdn; /* flag for modrdn */
+
+} repl;
+
+#define BIND_OK 0
+#define BIND_ERR_BADLDP 1
+#define BIND_ERR_OPEN 2
+#define BIND_ERR_BAD_ATYPE 3
+#define BIND_ERR_SIMPLE_FAILED 4
+#define BIND_ERR_KERBEROS_FAILED 5
+#define BIND_ERR_SSL_INIT_FAILED 6
+#define BIND_ERR_RACE 7
+
+#define MAX_CHANGENUMBER ULONG_MAX
+
+#define REPLICATION_SUBSYSTEM "replication"
+#define REPL_LDAP_TIMEOUT 30L /* Wait 30 seconds for responses */
+
+/* Update the copiedFrom attribute every <n> updates */
+#define UPDATE_COPIEDFROM_INTERVAL 10
+#define REPL_ERROR_REPL_HALTED "REPLICATION HALTED"
+#define ATTR_NETSCAPEMDSUFFIX "netscapemdsuffix"
+
+#define CONFIG_LEGACY_REPLICATIONDN_ATTRIBUTE "nsslapd-legacy-updatedn"
+#define CONFIG_LEGACY_REPLICATIONPW_ATTRIBUTE "nsslapd-legacy-updatepw"
+
+#define LDAP_CONTROL_REPL_MODRDN_EXTRAMODS "2.16.840.1.113730.3.4.999"
+
+/* Operation types */
+#define OP_MODIFY 1
+#define OP_ADD 2
+#define OP_DELETE 3
+#define OP_MODDN 4
+#define OP_SEARCH 5
+#define OP_COMPARE 6
+
+/* 4.0-style housekeeping interval */
+#define REPLICATION_HOUSEKEEPING_INTERVAL (30 * 1000) /* 30 seconds */
+
+/* Top of tree for replication configuration information */
+#define REPL_CONFIG_TOP "cn=replication,cn=config"
+
+/* Functions */
+
+/* repl_rootdse.c */
+int repl_rootdse_init();
+
+/* In repl.c */
+Slapi_Entry *get_changerecord(const chglog4Info *cl4, changeNumber cnum, int *err);
+changeNumber replog_get_firstchangenum(const chglog4Info *cl4, int *err);
+changeNumber replog_get_lastchangenum(const chglog4Info *cl4, int *err);
+void changelog_housekeeping(time_t cur_time );
+
+/* In repl_config.c */
+int repl_config_init ();
+
+/* Legacy Plugin Functions */
+
+int legacy_preop_bind( Slapi_PBlock *pb );
+int legacy_bepreop_bind( Slapi_PBlock *pb );
+int legacy_postop_bind( Slapi_PBlock *pb );
+int legacy_preop_add( Slapi_PBlock *pb );
+int legacy_bepreop_add( Slapi_PBlock *pb );
+int legacy_postop_add( Slapi_PBlock *pb );
+int legacy_preop_modify( Slapi_PBlock *pb );
+int legacy_bepreop_modify( Slapi_PBlock *pb );
+int legacy_postop_modify( Slapi_PBlock *pb );
+int legacy_preop_modrdn( Slapi_PBlock *pb );
+int legacy_bepreop_modrdn( Slapi_PBlock *pb );
+int legacy_postop_modrdn( Slapi_PBlock *pb );
+int legacy_preop_delete( Slapi_PBlock *pb );
+int legacy_bepreop_delete( Slapi_PBlock *pb );
+int legacy_postop_delete( Slapi_PBlock *pb );
+int legacy_preop_search( Slapi_PBlock *pb );
+int legacy_preop_compare( Slapi_PBlock *pb );
+int legacy_pre_entry( Slapi_PBlock *pb );
+int legacy_bepostop_assignchangenum( Slapi_PBlock *pb );
+
+int replication_plugin_start( Slapi_PBlock *pb );
+int replication_plugin_poststart( Slapi_PBlock *pb );
+int replication_plugin_stop( Slapi_PBlock *pb );
+
+/* In repl.c */
+void replog( Slapi_PBlock *pb, int optype );
+void init_changelog_trimming( changeNumber max_changes, time_t max_age );
+
+/* From repl_globals.c */
+
+extern char *attr_changenumber;
+extern char *attr_targetdn;
+extern char *attr_changetype;
+extern char *attr_newrdn;
+extern char *attr_deleteoldrdn;
+extern char *attr_changes;
+extern char *attr_newsuperior;
+extern char *attr_changetime;
+extern char *attr_dataversion;
+extern char *attr_csn;
+
+extern char *changetype_add;
+extern char *changetype_delete;
+extern char *changetype_modify;
+extern char *changetype_modrdn;
+extern char *changetype_moddn;
+
+extern char *type_copyingFrom;
+extern char *type_copiedFrom;
+extern char *filter_copyingFrom;
+extern char *filter_copiedFrom;
+extern char *filter_objectclass;
+
+extern char *type_cn;
+extern char *type_objectclass;
+
+/* JCMREPL - IFP should be defined centrally */
+
+#ifndef _IFP
+#define _IFP
+typedef int (*IFP)();
+#endif
+
+/* In cl4.c */
+
+changeNumber ldapi_assign_changenumber(chglog4Info *cl4);
+changeNumber ldapi_get_last_changenumber(chglog4Info *cl4);
+changeNumber ldapi_get_first_changenumber(chglog4Info *cl4);
+void ldapi_commit_changenumber(chglog4Info *cl4, changeNumber cnum);
+void ldapi_set_first_changenumber(chglog4Info *cl4, changeNumber cnum);
+void ldapi_set_last_changenumber(chglog4Info *cl4, changeNumber cnum);
+void ldapi_initialize_changenumbers(chglog4Info *cl4, changeNumber first, changeNumber last);
+
+#define LDBM_TYPE "ldbm"
+#define CHANGELOG_LDBM_TYPE "changelog-ldbm"
+
+#define MAX_RETRY_INTERVAL 3600 /* sec = 1 hour */
+
+#define REPL_PROTOCOL_UNKNOWN 0
+#define REPL_PROTOCOL_40 1
+#define REPL_PROTOCOL_50_INCREMENTAL 2
+#define REPL_PROTOCOL_50_TOTALUPDATE 3
+
+/* In repl_globals.c */
+int decrement_repl_active_threads();
+int increment_repl_active_threads();
+
+/* operation extensions */
+
+/* Type of extensions that can be registered */
+typedef enum
+{
+ REPL_SUP_EXT_OP, /* extension for Operation object, replication supplier */
+ REPL_SUP_EXT_CONN, /* extension for Connection object, replication supplier */
+ REPL_CON_EXT_OP, /* extension for Operation object, replication consumer */
+ REPL_CON_EXT_CONN, /* extension for Connection object, replication consumer */
+ REPL_CON_EXT_MTNODE,/* extension for mapping_tree_node object, replication consumer */
+ REPL_EXT_ALL
+} ext_type;
+
+/* general extension functions - repl_ext.c */
+void repl_sup_init_ext (); /* initializes registrations - must be called first */
+void repl_con_init_ext (); /* initializes registrations - must be called first */
+int repl_sup_register_ext (ext_type type); /* registers an extension of the specified type */
+int repl_con_register_ext (ext_type type); /* registers an extension of the specified type */
+void* repl_sup_get_ext (ext_type type, void *object); /* retireves the extension from the object */
+void* repl_con_get_ext (ext_type type, void *object); /* retireves the extension from the object */
+
+/* Operation extension functions - supplier_operation_extension.c */
+
+/* --- supplier operation extension --- */
+typedef struct supplier_operation_extension
+{
+ int prevent_recursive_call;
+ struct slapi_operation_parameters *operation_parameters;
+ char *repl_gen;
+} supplier_operation_extension;
+
+/* extension construct/destructor */
+void* supplier_operation_extension_constructor (void *object, void *parent);
+void supplier_operation_extension_destructor (void* ext,void *object, void *parent);
+
+/* --- consumer operation extension --- */
+typedef struct consumer_operation_extension
+{
+ int has_cf; /* non-zero if the operation contains a copiedFrom/copyingFrom attr */
+ void *search_referrals;
+} consumer_operation_extension;
+
+/* extension construct/destructor */
+void* consumer_operation_extension_constructor (void *object, void *parent);
+void consumer_operation_extension_destructor (void* ext,void *object, void *parent);
+
+/* Connection extension functions - repl_connext.c */
+
+/* --- connection extension --- */
+/* ONREPL - some pointers are void* because they represent 5.0 data structures
+ not known in this header. Fix */
+typedef struct consumer_connection_extension
+{
+ int is_legacy_replication_dn;
+ int repl_protocol_version; /* the replication protocol version number the supplier is talking. */
+ void *replica_acquired; /* Object* for replica */
+ void *supplier_ruv; /* RUV* */
+ int isreplicationsession;
+ Slapi_Connection *connection;
+} consumer_connection_extension;
+
+/* extension construct/destructor */
+void* consumer_connection_extension_constructor (void *object,void *parent);
+void consumer_connection_extension_destructor (void* ext,void *object,void *parent);
+
+/* mapping tree extension - stores replica object */
+typedef struct multimaster_mtnode_extension
+{
+ Object *replica;
+} multimaster_mtnode_extension;
+void* multimaster_mtnode_extension_constructor (void *object,void *parent);
+void multimaster_mtnode_extension_destructor (void* ext,void *object,void *parent);
+
+/* In repl_init.c */
+
+int get_legacy_stop();
+
+/* In repl_entry.c */
+void repl_entry_init(int argc, char** argv);
+
+/* In repl_ops.c */
+int legacy_preop( Slapi_PBlock *pb, const char* caller, int operation_type);
+int legacy_postop( Slapi_PBlock *pb, const char* caller, int operation_type);
+
+/* In profile.c */
+
+#ifdef PROFILE
+#define PROFILE_POINT if (CFG_profile) profile_log(__FILE__,__LINE__) /* JCMREPL - Where is the profiling flag stored? */
+#else
+#define PROFILE_POINT ((void)0)
+#endif
+
+void profile_log(char *file,int line);
+void profile_open();
+void profile_close();
+
+/* in repl_controls.c */
+void add_repl_control_mods( Slapi_PBlock *pb, Slapi_Mods *smods );
+
+/* ... */
+void create_entity (char* DN, const char* oclass);
+
+void write_replog_db( int optype, char *dn, void *change, int flag, changeNumber changenum, time_t curtime, LDAPMod **modrdn_mods );
+int entry2reple( Slapi_Entry *e, Slapi_Entry *oe );
+int mods2reple( Slapi_Entry *e, LDAPMod **ldm );
+int modrdn2reple( Slapi_Entry *e, char *newrdn, int deloldrdn, LDAPMod **ldm );
+
+/* In legacy_consumer.c */
+void process_legacy_cf(Slapi_PBlock *pb);
+int legacy_consumer_is_replicationdn(char *dn);
+int legacy_consumer_is_replicationpw(struct berval *creds);
+int legacy_consumer_config_init();
+
+/* function that gets called when a backend state is changed */
+void legacy_consumer_be_state_change (void *handle, char *be_name,
+ int old_be_state, int new_be_state);
+
+#endif /* _REPL_H_ */
+
+
+
diff --git a/ldap/servers/plugins/replication/repl5.h b/ldap/servers/plugins/replication/repl5.h
new file mode 100644
index 00000000..d936cbea
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl5.h
@@ -0,0 +1,480 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+/* repl5.h - 5.0 replication header */
+
+#ifndef _REPL5_H_
+#define _REPL5_H_
+
+#include <limits.h>
+#include <time.h>
+#include <stdio.h>
+#include <string.h>
+#ifndef _WIN32
+#include <sys/param.h>
+#endif /* _WIN32 */
+
+#include "portable.h" /* GGOODREPL - is this cheating? */
+#include "repl_shared.h"
+#include "llist.h"
+#include "repl5_ruv.h"
+#include "cl4.h"
+
+/* DS 5.0 replication protocol OIDs */
+#define REPL_START_NSDS50_REPLICATION_REQUEST_OID "2.16.840.1.113730.3.5.3"
+#define REPL_END_NSDS50_REPLICATION_REQUEST_OID "2.16.840.1.113730.3.5.5"
+#define REPL_NSDS50_REPLICATION_ENTRY_REQUEST_OID "2.16.840.1.113730.3.5.6"
+#define REPL_NSDS50_REPLICATION_RESPONSE_OID "2.16.840.1.113730.3.5.4"
+#define REPL_NSDS50_UPDATE_INFO_CONTROL_OID "2.16.840.1.113730.3.4.13"
+#define REPL_NSDS50_INCREMENTAL_PROTOCOL_OID "2.16.840.1.113730.3.6.1"
+#define REPL_NSDS50_TOTAL_PROTOCOL_OID "2.16.840.1.113730.3.6.2"
+
+/* DS 5.0 replication protocol error codes */
+#define NSDS50_REPL_REPLICA_READY 0x00 /* Replica ready, go ahead */
+#define NSDS50_REPL_REPLICA_BUSY 0x01 /* Replica busy, try later */
+#define NSDS50_REPL_EXCESSIVE_CLOCK_SKEW 0x02 /* Supplier clock too far ahead */
+#define NSDS50_REPL_PERMISSION_DENIED 0x03 /* Bind DN not allowed to send updates */
+#define NSDS50_REPL_DECODING_ERROR 0x04 /* Consumer couldn't decode extended operation */
+#define NSDS50_REPL_UNKNOWN_UPDATE_PROTOCOL 0x05 /* Consumer doesn't understand suplier's update protocol */
+#define NSDS50_REPL_NO_SUCH_REPLICA 0x06 /* Consumer holds no such replica */
+#define NSDS50_REPL_BELOW_PURGEPOINT 0x07 /* Supplier provided a CSN below the consumer's purge point */
+#define NSDS50_REPL_INTERNAL_ERROR 0x08 /* Something bad happened on consumer */
+#define NSDS50_REPL_REPLICA_RELEASE_SUCCEEDED 0x09 /* Replica released successfully */
+#define NSDS50_REPL_LEGACY_CONSUMER 0x0A /* replica is a legacy consumer */
+#define NSDS50_REPL_REPLICAID_ERROR 0x0B /* replicaID doesn't seem to be unique */
+#define NSDS50_REPL_DISABLED 0x0C /* replica suffix is disabled */
+#define NSDS50_REPL_UPTODATE 0x0D /* replica is uptodate */
+#define NSDS50_REPL_REPLICA_NO_RESPONSE 0xff /* No response received */
+
+/* Protocol status */
+#define PROTOCOL_STATUS_UNKNOWN 701
+#define PROTOCOL_STATUS_INCREMENTAL_AWAITING_CHANGES 702
+#define PROTOCOL_STATUS_INCREMENTAL_ACQUIRING_REPLICA 703
+#define PROTOCOL_STATUS_INCREMENTAL_RELEASING_REPLICA 704
+#define PROTOCOL_STATUS_INCREMENTAL_SENDING_UPDATES 705
+#define PROTOCOL_STATUS_INCREMENTAL_BACKING_OFF 706
+#define PROTOCOL_STATUS_INCREMENTAL_NEEDS_TOTAL_UPDATE 707
+#define PROTOCOL_STATUS_INCREMENTAL_FATAL_ERROR 708
+#define PROTOCOL_STATUS_TOTAL_ACQUIRING_REPLICA 709
+#define PROTOCOL_STATUS_TOTAL_RELEASING_REPLICA 710
+#define PROTOCOL_STATUS_TOTAL_SENDING_DATA 711
+
+/* To Allow Consumer Initialisation when adding an agreement - */
+#define STATE_PERFORMING_TOTAL_UPDATE 501
+#define STATE_PERFORMING_INCREMENTAL_UPDATE 502
+
+#define MAX_NUM_OF_MASTERS 64
+#define REPL_SESSION_ID_SIZE 64
+
+/* Attribute names for replication agreement attributes */
+extern const char *type_nsds5ReplicaHost;
+extern const char *type_nsds5ReplicaPort;
+extern const char *type_nsds5TransportInfo;
+extern const char *type_nsds5ReplicaBindDN;
+extern const char *type_nsds5ReplicaCredentials;
+extern const char *type_nsds5ReplicaBindMethod;
+extern const char *type_nsds5ReplicaRoot;
+extern const char *type_nsds5ReplicatedAttributeList;
+extern const char *type_nsds5ReplicaUpdateSchedule;
+extern const char *type_nsds5ReplicaInitialize;
+extern const char *type_nsds5ReplicaTimeout;
+extern const char *type_nsds5ReplicaBusyWaitTime;
+extern const char *type_nsds5ReplicaSessionPauseTime;
+
+/* To Allow Consumer Initialisation when adding an agreement - */
+extern const char *type_nsds5BeginReplicaRefresh;
+
+/* replica related attributes */
+extern const char *attr_replicaId;
+extern const char *attr_replicaRoot;
+extern const char *attr_replicaType;
+extern const char *attr_replicaBindDn;
+extern const char *attr_state;
+extern const char *attr_flags;
+extern const char *attr_replicaName;
+extern const char *attr_replicaReferral;
+extern const char *type_ruvElement;
+extern const char *type_replicaPurgeDelay;
+extern const char *type_replicaChangeCount;
+extern const char *type_replicaTombstonePurgeInterval;
+extern const char *type_replicaLegacyConsumer;
+extern const char *type_ruvElementUpdatetime;
+
+/* multimaster plugin points */
+int multimaster_preop_bind (Slapi_PBlock *pb);
+int multimaster_preop_add (Slapi_PBlock *pb);
+int multimaster_preop_delete (Slapi_PBlock *pb);
+int multimaster_preop_modify (Slapi_PBlock *pb);
+int multimaster_preop_modrdn (Slapi_PBlock *pb);
+int multimaster_preop_search (Slapi_PBlock *pb);
+int multimaster_preop_compare (Slapi_PBlock *pb);
+int multimaster_bepreop_add (Slapi_PBlock *pb);
+int multimaster_bepreop_delete (Slapi_PBlock *pb);
+int multimaster_bepreop_modify (Slapi_PBlock *pb);
+int multimaster_bepreop_modrdn (Slapi_PBlock *pb);
+int multimaster_bepostop_modrdn (Slapi_PBlock *pb);
+int multimaster_bepostop_delete (Slapi_PBlock *pb);
+int multimaster_postop_bind (Slapi_PBlock *pb);
+int multimaster_postop_add (Slapi_PBlock *pb);
+int multimaster_postop_delete (Slapi_PBlock *pb);
+int multimaster_postop_modify (Slapi_PBlock *pb);
+int multimaster_postop_modrdn (Slapi_PBlock *pb);
+
+/* In repl5_init.c */
+char* get_thread_private_agmtname ();
+void set_thread_private_agmtname (const char *agmtname);
+void* get_thread_private_cache ();
+void set_thread_private_cache (void *buf);
+char* get_repl_session_id (Slapi_PBlock *pb, char *id, CSN **opcsn);
+
+/* In repl_extop.c */
+int multimaster_extop_StartNSDS50ReplicationRequest(Slapi_PBlock *pb);
+int multimaster_extop_EndNSDS50ReplicationRequest(Slapi_PBlock *pb);
+int extop_noop(Slapi_PBlock *pb);
+struct berval *NSDS50StartReplicationRequest_new(const char *protocol_oid,
+ const char *repl_root, char **extra_referrals, CSN *csn);
+struct berval *NSDS50EndReplicationRequest_new(char *repl_root);
+int decode_repl_ext_response(struct berval *data, int *response_code,
+ struct berval ***ruv_bervals);
+
+/* In repl5_total.c */
+int multimaster_extop_NSDS50ReplicationEntry(Slapi_PBlock *pb);
+
+/* In repl_controls.c */
+int create_NSDS50ReplUpdateInfoControl(const char *uuid,
+ const char *superior_uuid, const CSN *csn,
+ LDAPMod **modify_mods, LDAPControl **ctrlp);
+void destroy_NSDS50ReplUpdateInfoControl(LDAPControl **ctrlp);
+int decode_NSDS50ReplUpdateInfoControl(LDAPControl **controlsp,
+ char **uuid, char **newsuperior_uuid, CSN **csn, LDAPMod ***modrdn_mods);
+
+/* In repl5_replsupplier.c */
+typedef struct repl_supplier Repl_Supplier;
+Repl_Supplier *replsupplier_init(Slapi_Entry *e);
+void replsupplier_configure(Repl_Supplier *rs, Slapi_PBlock *pb);
+void replsupplier_start(Repl_Supplier *rs);
+void replsupplier_stop(Repl_Supplier *rs);
+void replsupplier_destroy(Repl_Supplier **rs);
+void replsupplier_notify(Repl_Supplier *rs, PRUint32 eventmask);
+PRUint32 replsupplier_get_status(Repl_Supplier *rs);
+
+/* In repl5_plugins.c */
+int multimaster_set_local_purl();
+const char *multimaster_get_local_purl();
+PRBool multimaster_started();
+
+/* In repl5_schedule.c */
+typedef struct schedule Schedule;
+typedef void (*window_state_change_callback)(void *arg, PRBool opened);
+Schedule *schedule_new(window_state_change_callback callback_fn, void *callback_arg, const char *session_id);
+void schedule_destroy(Schedule *s);
+int schedule_set(Schedule *sch, Slapi_Attr *attr);
+char **schedule_get(Schedule *sch);
+int schedule_in_window_now(Schedule *sch);
+PRTime schedule_next(Schedule *sch);
+int schedule_notify(Schedule *sch, Slapi_PBlock *pb);
+void schedule_set_priority_attributes(Schedule *sch, char **prio_attrs, int override_schedule);
+void schedule_set_startup_delay(Schedule *sch, size_t startup_delay);
+void schedule_set_maximum_backlog(Schedule *sch, size_t max_backlog);
+void schedule_notify_session(Schedule *sch, PRTime session_end_time, unsigned int flags);
+#define REPLICATION_SESSION_SUCCESS 0
+
+/* In repl5_bos.c */
+typedef struct repl_bos Repl_Bos;
+
+/* In repl5_agmt.c */
+typedef struct repl5agmt Repl_Agmt;
+#define TRANSPORT_FLAG_SSL 1
+#define TRANSPORT_FLAG_TLS 2
+#define BINDMETHOD_SIMPLE_AUTH 1
+#define BINDMETHOD_SSL_CLIENTAUTH 2
+Repl_Agmt *agmt_new_from_entry(Slapi_Entry *e);
+Repl_Agmt *agmt_new_from_pblock(Slapi_PBlock *pb);
+void agmt_delete(void **ra);
+const Slapi_DN *agmt_get_dn_byref(const Repl_Agmt *ra);
+int agmt_get_auto_initialize(const Repl_Agmt *ra);
+long agmt_get_timeout(const Repl_Agmt *ra);
+long agmt_get_busywaittime(const Repl_Agmt *ra);
+long agmt_get_pausetime(const Repl_Agmt *ra);
+int agmt_start(Repl_Agmt *ra);
+int agmt_stop(Repl_Agmt *ra);
+int agmt_replicate_now(Repl_Agmt *ra);
+char *agmt_get_hostname(const Repl_Agmt *ra);
+int agmt_get_port(const Repl_Agmt *ra);
+PRUint32 agmt_get_transport_flags(const Repl_Agmt *ra);
+char *agmt_get_binddn(const Repl_Agmt *ra);
+struct berval *agmt_get_credentials(const Repl_Agmt *ra);
+int agmt_get_bindmethod(const Repl_Agmt *ra);
+Slapi_DN *agmt_get_replarea(const Repl_Agmt *ra);
+int agmt_is_fractional(const Repl_Agmt *ra);
+int agmt_is_fractional_attr(const Repl_Agmt *ra, const char *attrname);
+int agmt_is_50_mm_protocol(const Repl_Agmt *ra);
+int agmt_matches_name(const Repl_Agmt *ra, const Slapi_DN *name);
+int agmt_replarea_matches(const Repl_Agmt *ra, const Slapi_DN *name);
+int agmt_schedule_in_window_now(const Repl_Agmt *ra);
+int agmt_set_schedule_from_entry( Repl_Agmt *ra, const Slapi_Entry *e );
+int agmt_set_timeout_from_entry( Repl_Agmt *ra, const Slapi_Entry *e );
+int agmt_set_busywaittime_from_entry( Repl_Agmt *ra, const Slapi_Entry *e );
+int agmt_set_pausetime_from_entry( Repl_Agmt *ra, const Slapi_Entry *e );
+int agmt_set_credentials_from_entry( Repl_Agmt *ra, const Slapi_Entry *e );
+int agmt_set_binddn_from_entry( Repl_Agmt *ra, const Slapi_Entry *e );
+int agmt_set_bind_method_from_entry( Repl_Agmt *ra, const Slapi_Entry *e );
+int agmt_set_transportinfo_from_entry( Repl_Agmt *ra, const Slapi_Entry *e );
+const char *agmt_get_long_name(const Repl_Agmt *ra);
+int agmt_initialize_replica(const Repl_Agmt *agmt);
+void agmt_replica_init_done (const Repl_Agmt *agmt);
+void agmt_notify_change(Repl_Agmt *ra, Slapi_PBlock *pb);
+Object* agmt_get_consumer_ruv (Repl_Agmt *ra);
+ReplicaId agmt_get_consumer_rid ( Repl_Agmt *ra, void *conn );
+int agmt_set_consumer_ruv (Repl_Agmt *ra, RUV *ruv);
+void agmt_update_consumer_ruv (Repl_Agmt *ra);
+CSN* agmt_get_consumer_schema_csn (Repl_Agmt *ra);
+void agmt_set_consumer_schema_csn (Repl_Agmt *ra, CSN *csn);
+void agmt_set_last_update_in_progress (Repl_Agmt *ra, PRBool in_progress);
+void agmt_set_last_update_start (Repl_Agmt *ra, time_t start_time);
+void agmt_set_last_update_end (Repl_Agmt *ra, time_t end_time);
+void agmt_set_last_update_status (Repl_Agmt *ra, int ldaprc, int replrc, const char *msg);
+void agmt_set_update_in_progress (Repl_Agmt *ra, PRBool in_progress);
+void agmt_set_last_init_start (Repl_Agmt *ra, time_t start_time);
+void agmt_set_last_init_end (Repl_Agmt *ra, time_t end_time);
+void agmt_set_last_init_status (Repl_Agmt *ra, int ldaprc, int replrc, const char *msg);
+void agmt_inc_last_update_changecount (Repl_Agmt *ra, ReplicaId rid, int skipped);
+void agmt_get_changecount_string (Repl_Agmt *ra, char *buf, int bufsize);
+
+typedef struct replica Replica;
+
+/* In repl5_agmtlist.c */
+int agmtlist_config_init();
+void agmtlist_shutdown();
+void agmtlist_notify_all(Slapi_PBlock *pb);
+Object* agmtlist_get_first_agreement_for_replica (Replica *r);
+Object* agmtlist_get_next_agreement_for_replica (Replica *r, Object *prev);
+
+
+/* In repl5_backoff.c */
+typedef struct backoff_timer Backoff_Timer;
+#define BACKOFF_FIXED 1
+#define BACKOFF_EXPONENTIAL 2
+#define BACKOFF_RANDOM 3
+Backoff_Timer *backoff_new(int timer_type, int initial_interval, int max_interval);
+time_t backoff_reset(Backoff_Timer *bt, slapi_eq_fn_t callback, void *callback_data);
+time_t backoff_step(Backoff_Timer *bt);
+int backoff_expired(Backoff_Timer *bt, int margin);
+void backoff_delete(Backoff_Timer **btp);
+
+/* In repl5_connection.c */
+typedef struct repl_connection Repl_Connection;
+typedef enum
+{
+ CONN_OPERATION_SUCCESS,
+ CONN_OPERATION_FAILED,
+ CONN_NOT_CONNECTED,
+ CONN_SUPPORTS_DS5_REPL,
+ CONN_DOES_NOT_SUPPORT_DS5_REPL,
+ CONN_SCHEMA_UPDATED,
+ CONN_SCHEMA_NO_UPDATE_NEEDED,
+ CONN_LOCAL_ERROR,
+ CONN_BUSY,
+ CONN_SSL_NOT_ENABLED,
+ CONN_TIMEOUT
+} ConnResult;
+Repl_Connection *conn_new(Repl_Agmt *agmt);
+ConnResult conn_connect(Repl_Connection *conn);
+void conn_disconnect(Repl_Connection *conn);
+void conn_delete(Repl_Connection *conn);
+void conn_get_error(Repl_Connection *conn, int *operation, int *error);
+ConnResult conn_send_add(Repl_Connection *conn, const char *dn, LDAPMod **attrs,
+ LDAPControl *update_control, LDAPControl ***returned_controls);
+ConnResult conn_send_delete(Repl_Connection *conn, const char *dn,
+ LDAPControl *update_control, LDAPControl ***returned_controls);
+ConnResult conn_send_modify(Repl_Connection *conn, const char *dn, LDAPMod **mods,
+ LDAPControl *update_control, LDAPControl ***returned_controls);
+ConnResult conn_send_rename(Repl_Connection *conn, const char *dn,
+ const char *newrdn, const char *newparent, int deleteoldrdn,
+ LDAPControl *update_control, LDAPControl ***returned_controls);
+ConnResult conn_send_extended_operation(Repl_Connection *conn, const char *extop_oid,
+ struct berval *payload, char **retoidp, struct berval **retdatap,
+ LDAPControl *update_control, LDAPControl ***returned_controls);
+const char *conn_get_status(Repl_Connection *conn);
+void conn_start_linger(Repl_Connection *conn);
+void conn_cancel_linger(Repl_Connection *conn);
+ConnResult conn_replica_supports_ds5_repl(Repl_Connection *conn);
+ConnResult conn_read_entry_attribute(Repl_Connection *conn, const char *dn, char *type,
+ struct berval ***returned_bvals);
+ConnResult conn_push_schema(Repl_Connection *conn, CSN **remotecsn);
+void conn_set_timeout(Repl_Connection *conn, long timeout);
+void conn_set_agmt_changed(Repl_Connection *conn);
+
+/* In repl5_protocol.c */
+typedef struct repl_protocol Repl_Protocol;
+Repl_Protocol *prot_new(Repl_Agmt *agmt, int protocol_state);
+void prot_start(Repl_Protocol *rp);
+Repl_Agmt *prot_get_agreement(Repl_Protocol *rp);
+/* initiate total protocol */
+void prot_initialize_replica(Repl_Protocol *rp);
+/* stop protocol session in progress */
+void prot_stop(Repl_Protocol *rp);
+void prot_delete(Repl_Protocol **rpp);
+void prot_free(Repl_Protocol **rpp);
+PRBool prot_set_active_protocol (Repl_Protocol *rp, PRBool total);
+void prot_clear_active_protocol (Repl_Protocol *rp);
+Repl_Connection *prot_get_connection(Repl_Protocol *rp);
+void prot_resume(Repl_Protocol *rp, int wakeup_action);
+void prot_notify_update(Repl_Protocol *rp);
+void prot_notify_agmt_changed(Repl_Protocol *rp, char * agmt_name);
+void prot_notify_window_opened (Repl_Protocol *rp);
+void prot_notify_window_closed (Repl_Protocol *rp);
+Object *prot_get_replica_object(Repl_Protocol *rp);
+void prot_replicate_now(Repl_Protocol *rp);
+
+/* In repl5_replica.c */
+typedef enum
+{
+ REPLICA_TYPE_UNKNOWN,
+ REPLICA_TYPE_PRIMARY,
+ REPLICA_TYPE_READONLY,
+ REPLICA_TYPE_UPDATABLE,
+ REPLICA_TYPE_END
+} ReplicaType;
+
+#define RUV_STORAGE_ENTRY_UNIQUEID "ffffffff-ffffffff-ffffffff-ffffffff"
+#define START_ITERATION_ENTRY_UNIQUEID "00000000-00000000-00000000-00000000"
+#define START_ITERATION_ENTRY_DN "cn=start iteration"
+
+typedef int (*FNEnumReplica) (Replica *r, void *arg);
+
+/* this function should be called to construct the replica object
+ from the data already in the DIT */
+Replica *replica_new(const Slapi_DN *root);
+/* this function should be called to construct the replica object
+ during addition of the replica over LDAP */
+Replica *replica_new_from_entry (Slapi_Entry *e, char *errortext, PRBool is_add_operation);
+void replica_destroy(void **arg);
+PRBool replica_get_exclusive_access(Replica *r, PRBool *isInc, int connid, int opid,
+ const char *locking_purl,
+ char **current_purl);
+void replica_relinquish_exclusive_access(Replica *r, int connid, int opid);
+PRBool replica_get_tombstone_reap_active(const Replica *r);
+const Slapi_DN *replica_get_root(const Replica *r);
+const char *replica_get_name(const Replica *r);
+ReplicaId replica_get_rid (const Replica *r);
+void replica_set_rid (Replica *r, ReplicaId rid);
+PRBool replica_is_initialized (const Replica *r);
+Object *replica_get_ruv (const Replica *r);
+/* replica now owns the RUV */
+void replica_set_ruv (Replica *r, RUV *ruv);
+Object *replica_get_csngen (const Replica *r);
+ReplicaType replica_get_type (const Replica *r);
+void replica_set_type (Replica *r, ReplicaType type);
+PRBool replica_is_legacy_consumer (const Replica *r);
+void replica_set_legacy_consumer (Replica *r, PRBool legacy);
+char *replica_get_legacy_purl (const Replica *r);
+void replica_set_legacy_purl (Replica *r, const char *purl);
+PRBool replica_is_updatedn (const Replica *r, const Slapi_DN *sdn);
+void replica_set_updatedn (Replica *r, const Slapi_ValueSet *vs, int mod_op);
+char *replica_get_generation (const Replica *r);
+/* currently supported flags */
+#define REPLICA_LOG_CHANGES 0x1 /* enable change logging */
+PRBool replica_is_flag_set (const Replica *r, PRUint32 flag);
+void replica_set_flag (Replica *r, PRUint32 flag, PRBool clear);
+void replica_replace_flags (Replica *r, PRUint32 flags);
+void replica_dump(Replica *r);
+void replica_set_enabled (Replica *r, PRBool enable);
+Object *replica_get_replica_from_dn (const Slapi_DN *dn);
+void replica_update_ruv(Replica *replica, const CSN *csn, const char *replica_purl);
+Object *replica_get_replica_for_op (Slapi_PBlock *pb);
+/* the functions below manipulate replica hash */
+int replica_init_name_hash ();
+void replica_destroy_name_hash ();
+int replica_add_by_name (const char *name, Object *replica);
+int replica_delete_by_name (const char *name);
+Object* replica_get_by_name (const char *name);
+void replica_flush(Replica *r);
+void replica_get_referrals(const Replica *r, char ***referrals);
+void replica_set_referrals(Replica *r,const Slapi_ValueSet *vs);
+int replica_update_csngen_state (Replica *r, const RUV *ruv);
+CSN *replica_get_purge_csn(const Replica *r);
+int replica_log_ruv_elements (const Replica *r);
+void replica_enumerate_replicas (FNEnumReplica fn, void *arg);
+int replica_reload_ruv (Replica *r);
+int replica_check_for_data_reload (Replica *r, void *arg);
+/* the functions below manipulate replica dn hash */
+int replica_init_dn_hash ();
+void replica_destroy_dn_hash ();
+int replica_add_by_dn (const char *dn);
+int replica_delete_by_dn (const char *dn);
+int replica_is_being_configured (const char *dn);
+const CSN * _get_deletion_csn(Slapi_Entry *e);
+int legacy_consumer_init_referrals (Replica *r);
+void consumer5_set_mapping_tree_state_for_replica(const Replica *r, RUV *supplierRuv);
+Object *replica_get_for_backend (const char *be_name);
+void replica_set_purge_delay (Replica *r, PRUint32 purge_delay);
+void replica_set_tombstone_reap_interval (Replica *r, long interval);
+void replica_update_ruv_consumer (Replica *r, RUV *supplier_ruv);
+void replica_set_ruv_dirty (Replica *r);
+void replica_write_ruv (Replica *r);
+/* The functions below handles the state flag */
+/* Current internal state flags */
+/* The replica can be busy and not other flag,
+ * it means that the protocol has ended, but the work is not done yet.
+ * It happens on total protocol, the end protocol has been received,
+ * and the thread waits for import to finish
+ */
+#define REPLICA_IN_USE 1 /* The replica is busy */
+#define REPLICA_INCREMENTAL_IN_PROGRESS 2 /* Set only between start and stop inc */
+#define REPLICA_TOTAL_IN_PROGRESS 4 /* Set only between start and stop total */
+#define REPLICA_AGREEMENTS_DISABLED 8 /* Replica is offline */
+PRBool replica_is_state_flag_set(Replica *r, PRInt32 flag);
+void replica_set_state_flag (Replica *r, PRUint32 flag, PRBool clear);
+void replica_enable_replication (Replica *r);
+void replica_disable_replication (Replica *r, Object *r_obj);
+int replica_start_agreement(Replica *r, Repl_Agmt *ra);
+
+CSN* replica_generate_next_csn ( Slapi_PBlock *pb, const CSN *basecsn );
+int replica_get_attr ( Slapi_PBlock *pb, const char *type, void *value );
+
+/* mapping tree extensions manipulation */
+void multimaster_mtnode_extension_init ();
+void multimaster_mtnode_extension_destroy ();
+void multimaster_mtnode_construct_replicas ();
+
+void multimaster_be_state_change (void *handle, char *be_name, int old_be_state, int new_be_state);
+
+/* In repl5_replica_config.c */
+int replica_config_init();
+void replica_config_destroy ();
+
+/* replutil.c */
+LDAPControl* create_managedsait_control ();
+LDAPControl* create_backend_control(Slapi_DN *sdn);
+void repl_set_mtn_state_and_referrals(const Slapi_DN *sdn, const char *mtn_state,
+ const RUV *ruv, char **ruv_referrals,
+ char **other_referrals);
+void repl_set_repl_plugin_path(const char *path);
+
+/* repl5_updatedn_list.c */
+typedef void *ReplicaUpdateDNList;
+typedef int (*FNEnumDN)(Slapi_DN *dn, void *arg);
+ReplicaUpdateDNList replica_updatedn_list_new(const Slapi_Entry *entry);
+void replica_updatedn_list_free(ReplicaUpdateDNList list);
+void replica_updatedn_list_replace(ReplicaUpdateDNList list, const Slapi_ValueSet *vs);
+void replica_updatedn_list_delete(ReplicaUpdateDNList list, const Slapi_ValueSet *vs);
+void replica_updatedn_list_add(ReplicaUpdateDNList list, const Slapi_ValueSet *vs);
+PRBool replica_updatedn_list_ismember(ReplicaUpdateDNList list, const Slapi_DN *dn);
+char *replica_updatedn_list_to_string(ReplicaUpdateDNList list, const char *delimiter);
+void replica_updatedn_list_enumerate(ReplicaUpdateDNList list, FNEnumDN fn, void *arg);
+
+/* enabling developper traces for MMR to understand the total/inc protocol state machines */
+#ifdef DEV_DEBUG
+#define SLAPI_LOG_DEV_DEBUG SLAPI_LOG_FATAL
+#define dev_debug(a) slapi_log_error(SLAPI_LOG_DEV_DEBUG, "DEV_DEBUG", "%s\n", a)
+#else
+#define dev_debug(a)
+#endif
+
+void repl5_set_debug_timeout(const char *val);
+
+#endif /* _REPL5_H_ */
diff --git a/ldap/servers/plugins/replication/repl5_agmt.c b/ldap/servers/plugins/replication/repl5_agmt.c
new file mode 100644
index 00000000..2992fc11
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl5_agmt.c
@@ -0,0 +1,1766 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* repl5_agmt.c */
+/*
+
+ Support for 5.0-style replication agreements.
+
+ Directory Server 5.0 replication agreements contain information about
+ replication consumers that we are supplying.
+
+ This module encapsulates the methods available for adding, deleting,
+ modifying, and firing replication agreements.
+
+ Methods:
+
+ agmt_new - Create a new replication agreement, in response to a new
+ replication agreement being added over LDAP.
+ agmt_delete - Destroy an agreement. It is an error to destroy an
+ agreement that has not been stopped.
+ agmt_getstatus - get the status of this replication agreement.
+ agmt_replicate_now - initiate a replication session asap, even if the
+ schedule says we shouldn't.
+ agmt_start - start replicating, according to schedule. Starts a new
+ thread to handle replication.
+ agmt_stop - stop replicating asap and end replication thread.
+ agmt_notify_change - notify the replication agreement about a change that
+ has been logged. The replication agreement will
+ decide if it needs to take some action, e.g. start a
+ replication session.
+ agmt_initialize_replica - start a complete replica refresh.
+ agmt_set_schedule_from_entry - (re)set the schedule associated with this
+ replication agreement based on a RA entry's contents.
+ agmt_set_credentials_from_entry - (re)set the credentials used to bind
+ to the remote replica.
+ agmt_set_binddn_from_entry - (re)set the DN used to bind
+ to the remote replica.
+ agmt_set_bind_method_from_entry - (re)set the bind method used to bind
+ to the remote replica (SIMPLE or SSLCLIENTAUTH).
+ agmt_set_transportinfo_from_entry - (re)set the transport used to bind
+ to the remote replica (SSL or not)
+
+*/
+
+#include "repl5.h"
+#include "repl5_prot_private.h"
+#include "cl5_api.h"
+
+#define DEFAULT_TIMEOUT 600 /* (seconds) default outbound LDAP connection */
+#define TRANSPORT_FLAG_SSL 1
+#define STATUS_LEN 1024
+
+struct changecounter {
+ ReplicaId rid;
+ PRUint32 num_replayed;
+ PRUint32 num_skipped;
+};
+
+typedef struct repl5agmt {
+ char *hostname; /* remote hostname */
+ int port; /* port of remote server */
+ PRUint32 transport_flags; /* SSL, TLS, etc. */
+ char *binddn; /* DN to bind as */
+ struct berval *creds; /* Password, or certificate */
+ int bindmethod; /* Bind method - simple, SSL */
+ Slapi_DN *replarea; /* DN of replicated area */
+ char **frac_attrs; /* list of fractional attributes to be replicated */
+ Schedule *schedule; /* Scheduling information */
+ int auto_initialize; /* 1 = automatically re-initialize replica */
+ const Slapi_DN *dn; /* DN of replication agreement entry */
+ const Slapi_RDN *rdn; /* RDN of replication agreement entry */
+ char *long_name; /* Long name (rdn + host, port) of entry, for logging */
+ Repl_Protocol *protocol; /* Protocol object - manages protocol */
+ struct changecounter *changecounters[MAX_NUM_OF_MASTERS]; /* changes sent/skipped since server start up */
+ int num_changecounters;
+ time_t last_update_start_time; /* Local start time of last update session */
+ time_t last_update_end_time; /* Local end time of last update session */
+ char last_update_status[STATUS_LEN]; /* Status of last update. Format = numeric code <space> textual description */
+ PRBool update_in_progress;
+ time_t last_init_start_time; /* Local start time of last total init */
+ time_t last_init_end_time; /* Local end time of last total init */
+ char last_init_status[STATUS_LEN]; /* Status of last total init. Format = numeric code <space> textual description */
+ PRLock *lock;
+ Object *consumerRUV; /* last RUV received from the consumer - used for changelog purging */
+ CSN *consumerSchemaCSN; /* last schema CSN received from the consumer */
+ ReplicaId consumerRID; /* indicates if the consumer is the originator of a CSN */
+ long timeout; /* timeout (in seconds) for outbound LDAP connections to remote server */
+ PRBool stop_in_progress; /* set by agmt_stop when shutting down */
+ long busywaittime; /* time in seconds to wait after getting a REPLICA BUSY from the consumer -
+ to allow another supplier to finish sending its updates -
+ if set to 0, this means to use the default value if we get a busy
+ signal from the consumer */
+ long pausetime; /* time in seconds to pause after sending updates -
+ to allow another supplier to send its updates -
+ should be greater than busywaittime -
+ if set to 0, this means do not pause */
+} repl5agmt;
+
+/* Forward declarations */
+void agmt_delete(void **rap);
+static void update_window_state_change_callback (void *arg, PRBool opened);
+static int get_agmt_status(Slapi_PBlock *pb, Slapi_Entry* e,
+ Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg);
+static int agmt_set_bind_method_no_lock(Repl_Agmt *ra, const Slapi_Entry *e);
+static int agmt_set_transportinfo_no_lock(Repl_Agmt *ra, const Slapi_Entry *e);
+
+/*
+Schema for replication agreement:
+
+cn
+nsds5ReplicaHost - hostname
+nsds5ReplicaPort - port number
+nsds5ReplicaTransportInfo - "SSL", "startTLS", or may be absent;
+nsds5ReplicaBindDN
+nsds5ReplicaCredentials
+nsds5ReplicaBindMethod - "SIMPLE" or "SSLCLIENTAUTH".
+nsds5ReplicaRoot - Replicated suffix
+nsds5ReplicatedAttributeList - Unused so far (meant for fractional repl).
+nsds5ReplicaUpdateSchedule
+nsds5ReplicaTimeout - Outbound repl operations timeout
+nsds50ruv - consumer's RUV
+nsds5ReplicaBusyWaitTime - time to wait after getting a REPLICA BUSY from the consumer
+nsds5ReplicaSessionPauseTime - time to pause after sending updates to allow another supplier to send
+*/
+
+
+/*
+ * Validate an agreement, making sure that it's valid.
+ * Return 1 if the agreement is valid, 0 otherwise.
+ */
+static int
+agmt_is_valid(Repl_Agmt *ra)
+{
+ int return_value = 1; /* assume valid, initially */
+ PR_ASSERT(NULL != ra);
+ PR_ASSERT(NULL != ra->dn);
+
+ if (NULL == ra->hostname)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Replication agreement \"%s\" "
+ "is malformed: missing host name.\n", slapi_sdn_get_dn(ra->dn));
+ return_value = 0;
+ }
+ if (ra->port <= 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Replication agreement \"%s\" "
+ "is malformed: invalid port number %d.\n", slapi_sdn_get_dn(ra->dn), ra->port);
+ return_value = 0;
+ }
+ if (ra->timeout < 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Replication agreement \"%s\" "
+ "is malformed: invalid timeout %d.\n", slapi_sdn_get_dn(ra->dn), ra->timeout);
+ return_value = 0;
+ }
+ if (ra->busywaittime < 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Replication agreement \"%s\" "
+ "is malformed: invalid busy wait time %d.\n", slapi_sdn_get_dn(ra->dn), ra->busywaittime);
+ return_value = 0;
+ }
+ if (ra->pausetime < 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Replication agreement \"%s\" "
+ "is malformed: invalid pausetime %d.\n", slapi_sdn_get_dn(ra->dn), ra->pausetime);
+ return_value = 0;
+ }
+ return return_value;
+}
+
+
+Repl_Agmt *
+agmt_new_from_entry(Slapi_Entry *e)
+{
+ Repl_Agmt *ra;
+ char *tmpstr;
+ Slapi_Attr *sattr;
+
+ char *auto_initialize = NULL;
+ char *val_nsds5BeginReplicaRefresh = "start";
+
+ ra = (Repl_Agmt *)slapi_ch_calloc(1, sizeof(repl5agmt));
+ if ((ra->lock = PR_NewLock()) == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Unable to create new lock "
+ "for replication agreement \"%s\" - agreement ignored.\n",
+ slapi_entry_get_dn_const(e));
+ goto loser;
+ }
+
+ /* Find all the stuff we need for the agreement */
+
+ /* To Allow Consumer Initialisation when adding an agreement: */
+
+ /*
+ Using 'auto_initialize' member of 'repl5agmt' structure to
+ store the effect of 'nsds5BeginReplicaRefresh' attribute's value
+ in it.
+ */
+ auto_initialize = slapi_entry_attr_get_charptr(e, type_nsds5BeginReplicaRefresh);
+ if ((auto_initialize != NULL) && (strcasecmp(auto_initialize, val_nsds5BeginReplicaRefresh) == 0))
+ {
+ ra->auto_initialize = STATE_PERFORMING_TOTAL_UPDATE;
+ }
+ else
+ {
+ ra->auto_initialize = STATE_PERFORMING_INCREMENTAL_UPDATE;
+ }
+
+ if (auto_initialize)
+ {
+ slapi_ch_free_string (&auto_initialize);
+ }
+
+ /* Host name of remote replica */
+ ra->hostname = slapi_entry_attr_get_charptr(e, type_nsds5ReplicaHost);
+ /* Port number for remote replica instance */
+ ra->port = slapi_entry_attr_get_int(e, type_nsds5ReplicaPort);
+ /* SSL, TLS, or other transport stuff */
+ ra->transport_flags = 0;
+ agmt_set_transportinfo_no_lock(ra, e);
+
+ /* DN to use when binding. May be empty if cert-based auth is to be used. */
+ ra->binddn = slapi_entry_attr_get_charptr(e, type_nsds5ReplicaBindDN);
+ if (NULL == ra->binddn)
+ {
+ ra->binddn = slapi_ch_strdup("");
+ }
+ /* Credentials to use when binding. */
+ ra->creds = (struct berval *)slapi_ch_malloc(sizeof(struct berval));
+ ra->creds->bv_val = NULL;
+ ra->creds->bv_len = 0;
+ if (slapi_entry_attr_find(e, type_nsds5ReplicaCredentials, &sattr) == 0)
+ {
+ Slapi_Value *sval;
+ if (slapi_attr_first_value(sattr, &sval) == 0)
+ {
+ const struct berval *bv = slapi_value_get_berval(sval);
+ if (NULL != bv)
+ {
+ ra->creds->bv_val = slapi_ch_malloc(bv->bv_len + 1);
+ memcpy(ra->creds->bv_val, bv->bv_val, bv->bv_len);
+ ra->creds->bv_len = bv->bv_len;
+ ra->creds->bv_val[bv->bv_len] = '\0'; /* be safe */
+ }
+ }
+ }
+ /* How to bind */
+ (void)agmt_set_bind_method_no_lock(ra, e);
+
+ /* timeout. */
+ ra->timeout = DEFAULT_TIMEOUT;
+ if (slapi_entry_attr_find(e, type_nsds5ReplicaTimeout, &sattr) == 0)
+ {
+ Slapi_Value *sval;
+ if (slapi_attr_first_value(sattr, &sval) == 0)
+ {
+ ra->timeout = slapi_value_get_long(sval);
+ }
+ }
+
+ /* DN of entry at root of replicated area */
+ tmpstr = slapi_entry_attr_get_charptr(e, type_nsds5ReplicaRoot);
+ if (NULL != tmpstr)
+ {
+ ra->replarea = slapi_sdn_new_dn_passin(tmpstr);
+ }
+ /* XXXggood get fractional attribute include/exclude lists here */
+ /* Replication schedule */
+ ra->schedule = schedule_new(update_window_state_change_callback, ra, agmt_get_long_name(ra));
+ if (slapi_entry_attr_find(e, type_nsds5ReplicaUpdateSchedule, &sattr) == 0)
+ {
+ schedule_set(ra->schedule, sattr);
+ }
+
+ /* busy wait time - time to wait after getting REPLICA BUSY from consumer */
+ ra->busywaittime = slapi_entry_attr_get_long(e, type_nsds5ReplicaBusyWaitTime);
+
+ /* pause time - time to pause after a session has ended */
+ ra->pausetime = slapi_entry_attr_get_long(e, type_nsds5ReplicaSessionPauseTime);
+
+ /* consumer's RUV */
+ if (slapi_entry_attr_find(e, type_ruvElement, &sattr) == 0)
+ {
+ RUV *ruv;
+
+ if (ruv_init_from_slapi_attr(sattr, &ruv) == 0)
+ {
+ ra->consumerRUV = object_new (ruv, (FNFree)ruv_destroy);
+ }
+ }
+
+ ra->consumerRID = 0;
+
+ /* DN and RDN of the replication agreement entry itself */
+ ra->dn = slapi_sdn_dup(slapi_entry_get_sdn((Slapi_Entry *)e));
+ ra->rdn = slapi_rdn_new_sdn(ra->dn);
+
+ /* Compute long name */
+ {
+ const char *agmtname = slapi_rdn_get_rdn(ra->rdn);
+ char hostname[128];
+ char *dot;
+
+ strncpy(hostname, ra->hostname ? ra->hostname : "(unknown)", sizeof(hostname));
+ hostname[sizeof(hostname)-1] = '\0';
+ dot = strchr(hostname, '.');
+ if (dot) {
+ *dot = '\0';
+ }
+ ra->long_name = slapi_ch_malloc(strlen(agmtname) +
+ strlen(hostname) + 25);
+ sprintf(ra->long_name, "agmt=\"%s\" (%s:%d)", agmtname, hostname, ra->port);
+ }
+
+ /* Initialize status information */
+ ra->last_update_start_time = 0UL;
+ ra->last_update_end_time = 0UL;
+ ra->num_changecounters = 0;
+ ra->last_update_status[0] = '\0';
+ ra->update_in_progress = PR_FALSE;
+ ra->stop_in_progress = PR_FALSE;
+ ra->last_init_end_time = 0UL;
+ ra->last_init_start_time = 0UL;
+ ra->last_init_status[0] = '\0';
+
+ if (!agmt_is_valid(ra))
+ {
+ goto loser;
+ }
+
+ /* Now that the agreement is done, just check if changelog is configured */
+ if (cl5GetState() != CL5_STATE_OPEN) {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "WARNING: "
+ "Replication agreement added but there is no changelog configured. "
+ "No change will be replicated until a changelog is configured.\n");
+ }
+
+ /*
+ * Establish a callback for this agreement's entry, so we can
+ * adorn it with status information when read.
+ */
+ slapi_config_register_callback(SLAPI_OPERATION_SEARCH, DSE_FLAG_PREOP, slapi_sdn_get_ndn(ra->dn),
+ LDAP_SCOPE_BASE, "(objectclass=*)", get_agmt_status, ra);
+
+ return ra;
+loser:
+ agmt_delete((void **)&ra);
+ return NULL;
+}
+
+
+
+Repl_Agmt *
+agmt_new_from_pblock(Slapi_PBlock *pb)
+{
+ Slapi_Entry *e;
+
+ slapi_pblock_get(pb, SLAPI_ADD_ENTRY, &e);
+ return agmt_new_from_entry(e);
+}
+
+
+/*
+ This should never be called directly - only should be called
+ as a destructor. XXXggood this is not finished
+ */
+void
+agmt_delete(void **rap)
+{
+ Repl_Agmt *ra;
+ PR_ASSERT(NULL != rap);
+ PR_ASSERT(NULL != *rap);
+
+ ra = (Repl_Agmt *)*rap;
+
+ /* do prot_delete first - we may be doing some processing using this
+ replication agreement, and prot_delete will make sure the
+ processing is complete - then it should be safe to clean up the
+ other fields below
+ */
+ prot_delete(&ra->protocol);
+
+ /*
+ * Remove the callback for this agreement's entry
+ */
+ slapi_config_remove_callback(SLAPI_OPERATION_SEARCH, DSE_FLAG_PREOP,
+ slapi_sdn_get_ndn(ra->dn),
+ LDAP_SCOPE_BASE, "(objectclass=*)",
+ get_agmt_status);
+
+ /* slapi_ch_free accepts NULL pointer */
+ slapi_ch_free((void **)&(ra->hostname));
+ slapi_ch_free((void **)&(ra->binddn));
+
+ if (NULL != ra->creds)
+ {
+ /* XXX free berval */
+ }
+ if (NULL != ra->replarea)
+ {
+ slapi_sdn_free(&ra->replarea);
+ }
+
+ if (NULL != ra->consumerRUV)
+ {
+ object_release (ra->consumerRUV);
+ }
+
+ csn_free (&ra->consumerSchemaCSN);
+ while ( --(ra->num_changecounters) >= 0 )
+ {
+ slapi_ch_free((void **)&ra->changecounters[ra->num_changecounters]);
+ }
+
+ schedule_destroy(ra->schedule);
+ slapi_ch_free((void **)&ra->long_name);
+ slapi_ch_free((void **)rap);
+}
+
+
+/*
+ * Allow replication for this replica to begin. Replication will
+ * occur at the next scheduled time. Returns 0 on success, -1 on
+ * failure.
+ */
+int
+agmt_start(Repl_Agmt *ra)
+{
+ Repl_Protocol *prot = NULL;
+
+ int protocol_state;
+
+ /* To Allow Consumer Initialisation when adding an agreement: */
+ if (ra->auto_initialize == STATE_PERFORMING_TOTAL_UPDATE)
+ {
+ protocol_state = STATE_PERFORMING_TOTAL_UPDATE;
+ }
+ else
+ {
+ protocol_state = STATE_PERFORMING_INCREMENTAL_UPDATE;
+ }
+
+ /* First, create a new protocol object */
+ if ((prot = prot_new(ra, protocol_state)) == NULL) {
+ return -1;
+ }
+
+ /* Now it is safe to own the agreement lock */
+ PR_Lock(ra->lock);
+
+ /* Check that replication is not already started */
+ if (ra->protocol != NULL) {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "replication already started for agreement \"%s\"\n", agmt_get_long_name(ra));
+ PR_Unlock(ra->lock);
+ prot_free(&prot);
+ return 0;
+ }
+
+ ra->protocol = prot;
+
+ /* Start the protocol thread */
+ prot_start(ra->protocol);
+
+ PR_Unlock(ra->lock);
+ return 0;
+}
+
+/*
+Cease replicating to this replica as soon as possible.
+*/
+int
+agmt_stop(Repl_Agmt *ra)
+{
+ int return_value = 0;
+ Repl_Protocol *rp = NULL;
+
+ PR_Lock(ra->lock);
+ if (ra->stop_in_progress)
+ {
+ PR_Unlock(ra->lock);
+ return return_value;
+ }
+ ra->stop_in_progress = PR_TRUE;
+ rp = ra->protocol;
+ PR_Unlock(ra->lock);
+ if (NULL != rp) /* we use this pointer outside the lock - dangerous? */
+ {
+ prot_stop(rp);
+ }
+ PR_Lock(ra->lock);
+ ra->stop_in_progress = PR_FALSE;
+ /* we do not reuse the protocol object so free it */
+ prot_free(&ra->protocol);
+ PR_Unlock(ra->lock);
+ return return_value;
+}
+
+/*
+Send any pending updates as soon as possible, ignoring any replication
+schedules.
+*/
+int
+agmt_replicate_now(Repl_Agmt *ra)
+{
+ int return_value = 0;
+
+ return return_value;
+}
+
+/*
+ * Return a copy of the remote replica's hostname.
+ */
+char *
+agmt_get_hostname(const Repl_Agmt *ra)
+{
+ char *return_value;
+ PR_ASSERT(NULL != ra);
+ PR_Lock(ra->lock);
+ return_value = slapi_ch_strdup(ra->hostname);
+ PR_Unlock(ra->lock);
+ return return_value;
+}
+
+/*
+ * Return the port number of the remote replica's instance.
+ */
+int
+agmt_get_port(const Repl_Agmt *ra)
+{
+ int return_value;
+ PR_ASSERT(NULL != ra);
+ PR_Lock(ra->lock);
+ return_value = ra->port;
+ PR_Unlock(ra->lock);
+ return return_value;
+}
+
+/*
+ * Return the transport flags for this agreement.
+ */
+PRUint32
+agmt_get_transport_flags(const Repl_Agmt *ra)
+{
+ unsigned int return_value;
+ PR_ASSERT(NULL != ra);
+ PR_Lock(ra->lock);
+ return_value = ra->transport_flags;
+ PR_Unlock(ra->lock);
+ return return_value;
+}
+
+/*
+ * Return a copy of the bind dn to be used with this
+ * agreement (may return NULL if no binddn is required,
+ * e.g. SSL client auth.
+ */
+char *
+agmt_get_binddn(const Repl_Agmt *ra)
+{
+ char *return_value;
+ PR_ASSERT(NULL != ra);
+ PR_Lock(ra->lock);
+ return_value = ra->binddn == NULL ? NULL : slapi_ch_strdup(ra->binddn);
+ PR_Unlock(ra->lock);
+ return return_value;
+}
+
+/*
+ * Return a copy of the credentials.
+ */
+struct berval *
+agmt_get_credentials(const Repl_Agmt *ra)
+{
+ struct berval *return_value;
+ PR_ASSERT(NULL != ra);
+ PR_Lock(ra->lock);
+ return_value = (struct berval *)slapi_ch_malloc(sizeof(struct berval));
+ return_value->bv_val = (char *)slapi_ch_malloc(ra->creds->bv_len + 1);
+ return_value->bv_len = ra->creds->bv_len;
+ memcpy(return_value->bv_val, ra->creds->bv_val, ra->creds->bv_len);
+ return_value->bv_val[return_value->bv_len] = '\0'; /* just in case */
+ PR_Unlock(ra->lock);
+ return return_value;
+}
+
+int
+agmt_get_bindmethod(const Repl_Agmt *ra)
+{
+ int return_value;
+ PR_ASSERT(NULL != ra);
+ PR_Lock(ra->lock);
+ return_value = ra->bindmethod;
+ PR_Unlock(ra->lock);
+ return return_value;
+}
+
+/*
+ * Return a copy of the dn at the top of the replicated area.
+ */
+Slapi_DN *
+agmt_get_replarea(const Repl_Agmt *ra)
+{
+ Slapi_DN *return_value;
+ PR_ASSERT(NULL != ra);
+ PR_Lock(ra->lock);
+ return_value = slapi_sdn_new();
+ slapi_sdn_copy(ra->replarea, return_value);
+ PR_Unlock(ra->lock);
+ return return_value;
+}
+
+int
+agmt_is_fractional(const Repl_Agmt *ra)
+{
+ int return_value;
+ PR_ASSERT(NULL != ra);
+ PR_Lock(ra->lock);
+ return_value = ra->frac_attrs != NULL;
+ PR_Unlock(ra->lock);
+ return return_value;
+}
+
+int
+agmt_is_fractional_attr(const Repl_Agmt *ra, const char *attrname)
+{
+ int return_value;
+ PR_ASSERT(NULL != ra);
+ PR_Lock(ra->lock);
+ return_value = 1; /* XXXggood finish this */
+ PR_Unlock(ra->lock);
+ return return_value;
+}
+
+int
+agmt_get_auto_initialize(const Repl_Agmt *ra)
+{
+ int return_value;
+ PR_ASSERT(NULL != ra);
+ PR_Lock(ra->lock);
+ return_value = ra->auto_initialize;
+ PR_Unlock(ra->lock);
+ return return_value;
+}
+
+long
+agmt_get_timeout(const Repl_Agmt *ra)
+{
+ long return_value;
+ PR_ASSERT(NULL != ra);
+ PR_Lock(ra->lock);
+ return_value = ra->timeout;
+ PR_Unlock(ra->lock);
+ return return_value;
+}
+
+long
+agmt_get_busywaittime(const Repl_Agmt *ra)
+{
+ long return_value;
+ PR_ASSERT(NULL != ra);
+ PR_Lock(ra->lock);
+ return_value = ra->busywaittime;
+ PR_Unlock(ra->lock);
+ return return_value;
+}
+long
+agmt_get_pausetime(const Repl_Agmt *ra)
+{
+ long return_value;
+ PR_ASSERT(NULL != ra);
+ PR_Lock(ra->lock);
+ return_value = ra->pausetime;
+ PR_Unlock(ra->lock);
+ return return_value;
+}
+
+/*
+ * Warning - reference to the long name of the agreement is returned.
+ * The long name of an agreement is the DN of the agreement entry,
+ * followed by the host/port for the replica.
+ */
+const char *
+agmt_get_long_name(const Repl_Agmt *ra)
+{
+ char *return_value = NULL;
+
+ return_value = ra ? ra->long_name : "";
+ return return_value;
+}
+
+/*
+ * Warning - reference to dn is returned. However, since the dn of
+ * the replication agreement is its name, it won't change during the
+ * lifetime of the replication agreement object.
+ */
+const Slapi_DN *
+agmt_get_dn_byref(const Repl_Agmt *ra)
+{
+ const Slapi_DN *return_value = NULL;
+
+ PR_ASSERT(NULL != ra);
+ if (NULL != ra)
+ {
+ return_value = ra->dn;
+ }
+ return return_value;
+}
+
+/* Return 1 if name matches the replication Dn, 0 otherwise */
+int
+agmt_matches_name(const Repl_Agmt *ra, const Slapi_DN *name)
+{
+ int return_value = 0;
+ PR_ASSERT(NULL != ra);
+ if (NULL != ra)
+ {
+ PR_Lock(ra->lock);
+ if (slapi_sdn_compare(name, ra->dn) == 0)
+ {
+ return_value = 1;
+ }
+ PR_Unlock(ra->lock);
+ }
+ return return_value;
+}
+
+/* Return 1 if name matches the replication area, 0 otherwise */
+int
+agmt_replarea_matches(const Repl_Agmt *ra, const Slapi_DN *name)
+{
+ int return_value = 0;
+ PR_ASSERT(NULL != ra);
+ if (NULL != ra)
+ {
+ PR_Lock(ra->lock);
+ if (slapi_sdn_compare(name, ra->replarea) == 0)
+ {
+ return_value = 1;
+ }
+ PR_Unlock(ra->lock);
+ }
+ return return_value;
+}
+
+
+int
+agmt_schedule_in_window_now(const Repl_Agmt *ra)
+{
+ int return_value;
+ PR_ASSERT(NULL != ra);
+ PR_Lock(ra->lock);
+ if (NULL != ra->schedule && schedule_in_window_now(ra->schedule))
+ {
+ return_value = 1;
+ }
+ else
+ {
+ return_value = 0;
+ }
+ PR_Unlock(ra->lock);
+ return return_value;
+}
+
+
+/*
+ * Set or reset the credentials used to bind to the remote replica.
+ *
+ * Returns 0 if credentials set, or -1 if an error occurred.
+ */
+int
+agmt_set_credentials_from_entry(Repl_Agmt *ra, const Slapi_Entry *e)
+{
+ Slapi_Attr *sattr = NULL;
+ int return_value = 0;
+
+ PR_ASSERT(NULL != ra);
+ slapi_entry_attr_find(e, type_nsds5ReplicaCredentials, &sattr);
+ PR_Lock(ra->lock);
+ slapi_ch_free((void **)&ra->creds->bv_val);
+ ra->creds->bv_len = 0;
+ if (NULL != sattr)
+ {
+ Slapi_Value *sval = NULL;
+ slapi_attr_first_value(sattr, &sval);
+ if (NULL != sval)
+ {
+ const struct berval *bv = slapi_value_get_berval(sval);
+ ra->creds->bv_val = slapi_ch_calloc(1, bv->bv_len + 1);
+ memcpy(ra->creds->bv_val, bv->bv_val, bv->bv_len);
+ ra->creds->bv_len = bv->bv_len;
+ }
+ }
+ /* If no credentials set, set to zero-length string */
+ ra->creds->bv_val = NULL == ra->creds->bv_val ? slapi_ch_strdup("") : ra->creds->bv_val;
+ PR_Unlock(ra->lock);
+ prot_notify_agmt_changed(ra->protocol, ra->long_name);
+ return return_value;
+}
+
+/*
+ * Set or reset the DN used to bind to the remote replica.
+ *
+ * Returns 0 if DN set, or -1 if an error occurred.
+ */
+int
+agmt_set_binddn_from_entry(Repl_Agmt *ra, const Slapi_Entry *e)
+{
+ Slapi_Attr *sattr = NULL;
+ int return_value = 0;
+
+ PR_ASSERT(NULL != ra);
+ slapi_entry_attr_find(e, type_nsds5ReplicaBindDN, &sattr);
+ PR_Lock(ra->lock);
+ slapi_ch_free((void **)&ra->binddn);
+ ra->binddn = NULL;
+ if (NULL != sattr)
+ {
+ Slapi_Value *sval = NULL;
+ slapi_attr_first_value(sattr, &sval);
+ if (NULL != sval)
+ {
+ const char *val = slapi_value_get_string(sval);
+ ra->binddn = strdup(val);
+ }
+ }
+ /* If no BindDN set, set to zero-length string */
+ if (ra->binddn == NULL) {
+ ra->binddn = strdup("");
+ }
+ PR_Unlock(ra->lock);
+ prot_notify_agmt_changed(ra->protocol, ra->long_name);
+ return return_value;
+}
+
+/*
+ * Set or reset the bind method used to bind to the remote replica.
+ *
+ * Returns 0 if bind method set, or -1 if an error occurred.
+ */
+static int
+agmt_set_bind_method_no_lock(Repl_Agmt *ra, const Slapi_Entry *e)
+{
+ char *tmpstr = NULL;
+ int return_value = 0;
+
+ PR_ASSERT(NULL != ra);
+ tmpstr = slapi_entry_attr_get_charptr(e, type_nsds5ReplicaBindMethod);
+
+ if (NULL == tmpstr || strcasecmp(tmpstr, "SIMPLE") == 0)
+ {
+ ra->bindmethod = BINDMETHOD_SIMPLE_AUTH;
+ }
+ else if (strcasecmp(tmpstr, "SSLCLIENTAUTH") == 0)
+ {
+ ra->bindmethod = BINDMETHOD_SSL_CLIENTAUTH;
+ }
+ else
+ {
+ ra->bindmethod = BINDMETHOD_SIMPLE_AUTH;
+ }
+ slapi_ch_free((void **)&tmpstr);
+ return return_value;
+}
+
+int
+agmt_set_bind_method_from_entry(Repl_Agmt *ra, const Slapi_Entry *e)
+{
+ char *tmpstr = NULL;
+ int return_value = 0;
+
+ PR_ASSERT(NULL != ra);
+ PR_Lock(ra->lock);
+ if (ra->stop_in_progress)
+ {
+ PR_Unlock(ra->lock);
+ return return_value;
+ }
+ return_value = agmt_set_bind_method_no_lock(ra, e);
+ PR_Unlock(ra->lock);
+ prot_notify_agmt_changed(ra->protocol, ra->long_name);
+ return return_value;
+}
+
+/*
+ * Set or reset the transport used to bind to the remote replica.
+ *
+ * Returns 0 if transport set, or -1 if an error occurred.
+ */
+static int
+agmt_set_transportinfo_no_lock(Repl_Agmt *ra, const Slapi_Entry *e)
+{
+ char *tmpstr;
+ int rc = 0;
+
+ tmpstr = slapi_entry_attr_get_charptr(e, type_nsds5TransportInfo);
+ if (NULL != tmpstr && strcasecmp(tmpstr, "SSL") == 0)
+ {
+ ra->transport_flags |= TRANSPORT_FLAG_SSL;
+ } else {
+ ra->transport_flags &= ~TRANSPORT_FLAG_SSL;
+ }
+
+ slapi_ch_free((void **)&tmpstr);
+ return (rc);
+}
+
+int
+agmt_set_transportinfo_from_entry(Repl_Agmt *ra, const Slapi_Entry *e)
+{
+ int return_value = 0;
+
+ PR_ASSERT(NULL != ra);
+ PR_Lock(ra->lock);
+ if (ra->stop_in_progress)
+ {
+ PR_Unlock(ra->lock);
+ return return_value;
+ }
+ return_value = agmt_set_transportinfo_no_lock(ra, e);
+ PR_Unlock(ra->lock);
+ prot_notify_agmt_changed(ra->protocol, ra->long_name);
+
+ return return_value;
+}
+
+
+/*
+ * Set or reset the replication schedule. Notify the protocol handler
+ * that a change has been made.
+ *
+ * Returns 0 if schedule was set or -1 if an error occurred.
+ */
+int
+agmt_set_schedule_from_entry( Repl_Agmt *ra, const Slapi_Entry *e )
+{
+ Slapi_Attr *sattr;
+ int return_value = 0;
+
+ PR_ASSERT(NULL != ra);
+ PR_Lock(ra->lock);
+ if (ra->stop_in_progress)
+ {
+ PR_Unlock(ra->lock);
+ return return_value;
+ }
+ PR_Unlock(ra->lock);
+
+ if (slapi_entry_attr_find(e, type_nsds5ReplicaUpdateSchedule, &sattr) != 0)
+ {
+ sattr = NULL; /* no schedule ==> delete any existing one */
+ }
+
+ /* make it so */
+ return_value = schedule_set(ra->schedule, sattr);
+
+ if ( 0 == return_value ) {
+ /* schedule set OK -- spread the news */
+ prot_notify_agmt_changed(ra->protocol, ra->long_name);
+ }
+
+ return return_value;
+}
+
+/*
+ * Set or reset the timeout used to bind to the remote replica.
+ *
+ * Returns 0 if timeout set, or -1 if an error occurred.
+ */
+int
+agmt_set_timeout_from_entry(Repl_Agmt *ra, const Slapi_Entry *e)
+{
+ Slapi_Attr *sattr = NULL;
+ int return_value = -1;
+
+ PR_ASSERT(NULL != ra);
+ PR_Lock(ra->lock);
+ if (ra->stop_in_progress)
+ {
+ PR_Unlock(ra->lock);
+ return return_value;
+ }
+
+ slapi_entry_attr_find(e, type_nsds5ReplicaTimeout, &sattr);
+ if (NULL != sattr)
+ {
+ Slapi_Value *sval = NULL;
+ slapi_attr_first_value(sattr, &sval);
+ if (NULL != sval)
+ {
+ long tmpval = slapi_value_get_long(sval);
+ if (tmpval >= 0) {
+ ra->timeout = tmpval;
+ return_value = 0; /* success! */
+ }
+ }
+ }
+ PR_Unlock(ra->lock);
+ if (return_value == 0)
+ {
+ prot_notify_agmt_changed(ra->protocol, ra->long_name);
+ }
+ return return_value;
+}
+
+/*
+ * Set or reset the busywaittime
+ *
+ * Returns 0 if busywaittime set, or -1 if an error occurred.
+ */
+int
+agmt_set_busywaittime_from_entry(Repl_Agmt *ra, const Slapi_Entry *e)
+{
+ Slapi_Attr *sattr = NULL;
+ int return_value = -1;
+
+ PR_ASSERT(NULL != ra);
+ PR_Lock(ra->lock);
+ if (ra->stop_in_progress)
+ {
+ PR_Unlock(ra->lock);
+ return return_value;
+ }
+
+ slapi_entry_attr_find(e, type_nsds5ReplicaBusyWaitTime, &sattr);
+ if (NULL != sattr)
+ {
+ Slapi_Value *sval = NULL;
+ slapi_attr_first_value(sattr, &sval);
+ if (NULL != sval)
+ {
+ long tmpval = slapi_value_get_long(sval);
+ if (tmpval >= 0) {
+ ra->busywaittime = tmpval;
+ return_value = 0; /* success! */
+ }
+ }
+ }
+ PR_Unlock(ra->lock);
+ if (return_value == 0)
+ {
+ prot_notify_agmt_changed(ra->protocol, ra->long_name);
+ }
+ return return_value;
+}
+
+/*
+ * Set or reset the pausetime
+ *
+ * Returns 0 if pausetime set, or -1 if an error occurred.
+ */
+int
+agmt_set_pausetime_from_entry(Repl_Agmt *ra, const Slapi_Entry *e)
+{
+ Slapi_Attr *sattr = NULL;
+ int return_value = -1;
+
+ PR_ASSERT(NULL != ra);
+ PR_Lock(ra->lock);
+ if (ra->stop_in_progress)
+ {
+ PR_Unlock(ra->lock);
+ return return_value;
+ }
+
+ slapi_entry_attr_find(e, type_nsds5ReplicaSessionPauseTime, &sattr);
+ if (NULL != sattr)
+ {
+ Slapi_Value *sval = NULL;
+ slapi_attr_first_value(sattr, &sval);
+ if (NULL != sval)
+ {
+ long tmpval = slapi_value_get_long(sval);
+ if (tmpval >= 0) {
+ ra->pausetime = tmpval;
+ return_value = 0; /* success! */
+ }
+ }
+ }
+ PR_Unlock(ra->lock);
+ if (return_value == 0)
+ {
+ prot_notify_agmt_changed(ra->protocol, ra->long_name);
+ }
+ return return_value;
+}
+
+/* XXXggood - also make this pass an arg that tells if there was
+ * an update to a priority attribute */
+void
+agmt_notify_change(Repl_Agmt *agmt, Slapi_PBlock *pb)
+{
+ if (NULL != pb)
+ {
+ /* Is the entry within our replicated area? */
+ char *target_dn;
+ Slapi_DN *target_sdn;
+ int change_is_relevant = 0;
+
+ PR_ASSERT(NULL != agmt);
+ PR_Lock(agmt->lock);
+ if (agmt->stop_in_progress)
+ {
+ PR_Unlock(agmt->lock);
+ return;
+ }
+
+ slapi_pblock_get(pb, SLAPI_TARGET_DN, &target_dn);
+ target_sdn = slapi_sdn_new_dn_byref(target_dn); /* XXX see if you can avoid allocating this */
+
+ if (slapi_sdn_issuffix(target_sdn, agmt->replarea))
+ {
+ /*
+ * Yep, it's in our replicated area. Is this a fractional
+ * replication agreement?
+ */
+ if (NULL != agmt->frac_attrs)
+ {
+ /*
+ * Yep, it's fractional. See if the change should be
+ * tossed because it doesn't affect any of the replicated
+ * attributes.
+ */
+ int optype;
+ int affects_fractional_attribute = 0;
+
+ slapi_pblock_get(pb, SLAPI_OPERATION_TYPE, &optype);
+ if (SLAPI_OPERATION_MODIFY == optype)
+ {
+ LDAPMod **mods;
+ int i, j;
+
+ slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods);
+ for (i = 0; !affects_fractional_attribute && NULL != agmt->frac_attrs[i]; i++)
+ {
+ for (j = 0; !affects_fractional_attribute && NULL != mods[j]; j++)
+ {
+ if (slapi_attr_types_equivalent(agmt->frac_attrs[i],
+ mods[i]->mod_type))
+ {
+ affects_fractional_attribute = 1;
+ }
+ }
+ }
+ }
+ else
+ {
+ /*
+ * Add, delete, and modrdn always cause some sort of
+ * operation replay, even if agreement is fractional.
+ */
+ affects_fractional_attribute = 1;
+ }
+ if (affects_fractional_attribute)
+ {
+ change_is_relevant = 1;
+ }
+ }
+ else
+ {
+ /* Not a fractional agreement */
+ change_is_relevant = 1;
+ }
+ }
+ PR_Unlock(agmt->lock);
+ slapi_sdn_free(&target_sdn);
+ if (change_is_relevant)
+ {
+ /* Notify the protocol that a change has occurred */
+ prot_notify_update(agmt->protocol);
+ }
+ }
+}
+
+
+
+int
+agmt_is_50_mm_protocol(const Repl_Agmt *agmt)
+{
+ return 1; /* XXXggood could support > 1 protocol */
+}
+
+
+
+int
+agmt_initialize_replica(const Repl_Agmt *agmt)
+{
+ PR_ASSERT(NULL != agmt);
+ PR_Lock(agmt->lock);
+ if (agmt->stop_in_progress)
+ {
+ PR_Unlock(agmt->lock);
+ return 0;
+ }
+ PR_Unlock(agmt->lock);
+ /* Call prot_initialize_replica only if the suffix is enabled (agmt->protocol != NULL) */
+ if (NULL != agmt->protocol) {
+ prot_initialize_replica(agmt->protocol);
+ }
+ else {
+ /* agmt->protocol == NULL --> Suffix is disabled */
+ return -1;
+ }
+ return 0;
+}
+
+/* delete nsds5BeginReplicaRefresh attribute to indicate to the clients
+ that replica initialization have completed */
+void
+agmt_replica_init_done (const Repl_Agmt *agmt)
+{
+ int rc;
+ Slapi_PBlock *pb = slapi_pblock_new ();
+ LDAPMod *mods [2];
+ LDAPMod mod;
+
+ mods[0] = &mod;
+ mods[1] = NULL;
+ mod.mod_op = LDAP_MOD_DELETE | LDAP_MOD_BVALUES;
+ mod.mod_type = (char*)type_nsds5ReplicaInitialize;
+ mod.mod_bvalues = NULL;
+
+ slapi_modify_internal_set_pb(pb, slapi_sdn_get_dn (agmt->dn), mods, NULL/* controls */,
+ NULL/* uniqueid */, repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0/* flags */);
+ slapi_modify_internal_pb (pb);
+
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
+ if (rc != LDAP_SUCCESS && rc != LDAP_NO_SUCH_ATTRIBUTE)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "agmt_replica_init_done: "
+ "failed to remove (%s) attribute from (%s) entry; LDAP error - %d\n",
+ type_nsds5ReplicaInitialize, slapi_sdn_get_ndn (agmt->dn), rc);
+ }
+
+ slapi_pblock_destroy (pb);
+}
+
+/* Agreement object is acquired on behalf of the caller.
+ The caller is responsible for releasing the object
+ when it is no longer used */
+
+Object*
+agmt_get_consumer_ruv (Repl_Agmt *ra)
+{
+ Object *rt = NULL;
+
+ PR_ASSERT(NULL != ra);
+
+ PR_Lock(ra->lock);
+ if (ra->consumerRUV)
+ {
+ object_acquire (ra->consumerRUV);
+ rt = ra->consumerRUV;
+ }
+
+ PR_Unlock(ra->lock);
+
+ return rt;
+}
+
+int
+agmt_set_consumer_ruv (Repl_Agmt *ra, RUV *ruv)
+{
+ if (ra == NULL || ruv == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmt_set_consumer_ruv: invalid argument"
+ " agmt - %p, ruv - %p\n", ra, ruv);
+ return -1;
+ }
+
+ PR_Lock(ra->lock);
+
+ if (ra->consumerRUV)
+ {
+ object_release (ra->consumerRUV);
+ }
+
+ ra->consumerRUV = object_new (ruv_dup (ruv), (FNFree)ruv_destroy);
+
+ PR_Unlock(ra->lock);
+
+ return 0;
+}
+
+void
+agmt_update_consumer_ruv (Repl_Agmt *ra)
+{
+ int rc;
+ RUV *ruv;
+ Slapi_Mod smod;
+ Slapi_Mod smod_last_modified;
+ Slapi_PBlock *pb;
+ LDAPMod *mods[3];
+
+ PR_ASSERT (ra);
+ PR_Lock(ra->lock);
+
+ if (ra->consumerRUV)
+ {
+ ruv = (RUV*) object_get_data (ra->consumerRUV);
+ PR_ASSERT (ruv);
+
+ ruv_to_smod(ruv, &smod);
+ ruv_last_modified_to_smod(ruv, &smod_last_modified);
+
+ /* it is ok to release the lock here because we are done with the agreement data.
+ we have to do it before issuing the modify operation because it causes
+ agmtlist_notify_all to be called which uses the same lock - hence the deadlock */
+ PR_Unlock(ra->lock);
+
+ pb = slapi_pblock_new ();
+ mods[0] = (LDAPMod *)slapi_mod_get_ldapmod_byref(&smod);
+ mods[1] = (LDAPMod *)slapi_mod_get_ldapmod_byref(&smod_last_modified);
+ mods[2] = NULL;
+
+ slapi_modify_internal_set_pb (pb, (char*)slapi_sdn_get_dn(ra->dn), mods, NULL, NULL,
+ repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION), 0);
+ slapi_modify_internal_pb (pb);
+
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
+ if (rc != LDAP_SUCCESS && rc != LDAP_NO_SUCH_ATTRIBUTE)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "%s: agmt_update_consumer_ruv: "
+ "failed to update consumer's RUV; LDAP error - %d\n",
+ ra->long_name, rc);
+ }
+
+ slapi_mod_done (&smod);
+ slapi_mod_done (&smod_last_modified);
+ slapi_pblock_destroy (pb);
+ }
+ else
+ PR_Unlock(ra->lock);
+}
+
+CSN*
+agmt_get_consumer_schema_csn (Repl_Agmt *ra)
+{
+ CSN *rt;
+
+ PR_ASSERT(NULL != ra);
+
+ PR_Lock(ra->lock);
+ rt = ra->consumerSchemaCSN;
+ PR_Unlock(ra->lock);
+
+ return rt;
+}
+
+void
+agmt_set_consumer_schema_csn (Repl_Agmt *ra, CSN *csn)
+{
+ PR_ASSERT(NULL != ra);
+
+ PR_Lock(ra->lock);
+ csn_free(&ra->consumerSchemaCSN);
+ ra->consumerSchemaCSN = csn;
+ PR_Unlock(ra->lock);
+}
+
+void
+agmt_set_last_update_start (Repl_Agmt *ra, time_t start_time)
+{
+ PR_ASSERT(NULL != ra);
+ if (NULL != ra)
+ {
+ ra->last_update_start_time = start_time;
+ ra->last_update_end_time = 0UL;
+ }
+}
+
+
+void
+agmt_set_last_update_end (Repl_Agmt *ra, time_t end_time)
+{
+ PR_ASSERT(NULL != ra);
+ if (NULL != ra)
+ {
+ ra->last_update_end_time = end_time;
+ }
+}
+
+void
+agmt_set_last_init_start (Repl_Agmt *ra, time_t start_time)
+{
+ PR_ASSERT(NULL != ra);
+ if (NULL != ra)
+ {
+ ra->last_init_start_time = start_time;
+ ra->last_init_end_time = 0UL;
+ }
+}
+
+
+void
+agmt_set_last_init_end (Repl_Agmt *ra, time_t end_time)
+{
+ PR_ASSERT(NULL != ra);
+ if (NULL != ra)
+ {
+ ra->last_init_end_time = end_time;
+ }
+}
+
+void
+agmt_set_last_update_status (Repl_Agmt *ra, int ldaprc, int replrc, const char *message)
+{
+ PR_ASSERT(NULL != ra);
+ if (NULL != ra)
+ {
+ if (replrc == NSDS50_REPL_UPTODATE)
+ {
+ /* no session started, no status update */
+ }
+ else if (ldaprc != LDAP_SUCCESS)
+ {
+ char *replmsg = NULL;
+ if ( replrc ) {
+ replmsg = protocol_response2string(replrc);
+ /* Do not mix the unknown replication error with the known ldap one */
+ if ( strcasecmp(replmsg, "unknown error") == 0 ) {
+ replmsg = NULL;
+ }
+ }
+ if (ldaprc > 0) {
+ PR_snprintf(ra->last_update_status, STATUS_LEN,
+ "%d %s%sLDAP error: %s%s%s",
+ ldaprc,
+ message?message:"",message?"":" - ",
+ ldap_err2string(ldaprc),
+ replmsg ? " - " : "", replmsg ? replmsg : "");
+ } else { /* ldaprc is < 0 */
+ PR_snprintf(ra->last_update_status, STATUS_LEN,
+ "%d %s%sSystem error%s%s",
+ ldaprc,message?message:"",message?"":" - ",
+ replmsg ? " - " : "", replmsg ? replmsg : "");
+ }
+ }
+ else if (replrc != 0)
+ {
+ if (replrc == NSDS50_REPL_REPLICA_READY)
+ {
+ PR_snprintf(ra->last_update_status, STATUS_LEN, "%d %s",
+ ldaprc, "Replica acquired successfully");
+ }
+ else if (replrc == NSDS50_REPL_REPLICA_BUSY)
+ {
+ PR_snprintf(ra->last_update_status, STATUS_LEN,
+ "%d Can't acquire busy replica", replrc );
+ }
+ else if (replrc == NSDS50_REPL_REPLICA_RELEASE_SUCCEEDED)
+ {
+ PR_snprintf(ra->last_update_status, STATUS_LEN, "%d %s",
+ ldaprc, "Replication session successful");
+ }
+ else if (replrc == NSDS50_REPL_DISABLED)
+ {
+ PR_snprintf(ra->last_update_status, STATUS_LEN, "%d Total update aborted: "
+ "Replication agreement for %s\n can not be updated while the replica is disabled.\n"
+ "(If the suffix is disabled you must enable it then restart the server for replication to take place).",
+ replrc, ra->long_name ? ra->long_name : "a replica");
+ /* Log into the errors log, as "ra->long_name" is not accessible from the caller */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "Total update aborted: Replication agreement for \"%s\" "
+ "can not be updated while the replica is disabled\n", ra->long_name ? ra->long_name : "a replica");
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "(If the suffix is disabled you must enable it then restart the server for replication to take place).\n");
+ }
+ else
+ {
+ PR_snprintf(ra->last_update_status, STATUS_LEN,
+ "%d Replication error acquiring replica: %s%s%s",
+ replrc, protocol_response2string(replrc),
+ message?" - ":"",message?message:"");
+ }
+ }
+ else if (message != NULL)
+ {
+ PR_snprintf(ra->last_update_status, STATUS_LEN, "%d %s", ldaprc, message);
+ }
+ else { /* agmt_set_last_update_status(0,0,NULL) to reset agmt */
+ PR_snprintf(ra->last_update_status, STATUS_LEN, "%d", ldaprc);
+ }
+ }
+}
+
+void
+agmt_set_last_init_status (Repl_Agmt *ra, int ldaprc, int replrc, const char *message)
+{
+ PR_ASSERT(NULL != ra);
+ if (NULL != ra)
+ {
+ if (ldaprc != LDAP_SUCCESS)
+ {
+ char *replmsg = NULL;
+ if ( replrc ) {
+ replmsg = protocol_response2string(replrc);
+ /* Do not mix the unknown replication error with the known ldap one */
+ if ( strcasecmp(replmsg, "unknown error") == 0 ) {
+ replmsg = NULL;
+ }
+ }
+ if (ldaprc > 0) {
+ PR_snprintf(ra->last_init_status, STATUS_LEN,
+ "%d %s%sLDAP error: %s%s%s",
+ ldaprc,
+ message?message:"",message?"":" - ",
+ ldap_err2string(ldaprc),
+ replmsg ? " - " : "", replmsg ? replmsg : "");
+ } else { /* ldaprc is < 0 */
+ PR_snprintf(ra->last_init_status, STATUS_LEN,
+ "%d %s%sSystem error%s%s",
+ ldaprc,message?message:"",message?"":" - ",
+ replmsg ? " - " : "", replmsg ? replmsg : "");
+ }
+ }
+ else if (replrc != 0)
+ {
+ if (replrc == NSDS50_REPL_REPLICA_READY)
+ {
+ PR_snprintf(ra->last_init_status, STATUS_LEN, "%d %s",
+ ldaprc, "Replica acquired successfully");
+ }
+ else if (replrc == NSDS50_REPL_REPLICA_RELEASE_SUCCEEDED)
+ {
+ PR_snprintf(ra->last_init_status, STATUS_LEN, "%d %s",
+ ldaprc, "Replication session successful");
+ }
+ else if (replrc == NSDS50_REPL_DISABLED)
+ {
+ PR_snprintf(ra->last_init_status, STATUS_LEN, "%d Total update aborted: "
+ "Replication agreement for %s\n can not be updated while the replica is disabled.\n"
+ "(If the suffix is disabled you must enable it then restart the server for replication to take place).",
+ replrc, ra->long_name ? ra->long_name : "a replica");
+ /* Log into the errors log, as "ra->long_name" is not accessible from the caller */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "Total update aborted: Replication agreement for \"%s\" "
+ "can not be updated while the replica is disabled\n", ra->long_name ? ra->long_name : "a replica");
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "(If the suffix is disabled you must enable it then restart the server for replication to take place).\n");
+ }
+ else
+ {
+ PR_snprintf(ra->last_init_status, STATUS_LEN,
+ "%d Replication error acquiring replica: %s%s%s",
+ replrc, protocol_response2string(replrc),
+ message?" - ":"",message?message:"");
+ }
+ }
+ else if (message != NULL)
+ {
+ PR_snprintf(ra->last_init_status, STATUS_LEN, "%d %s", ldaprc, message);
+ }
+ else { /* agmt_set_last_init_status(0,0,NULL) to reset agmt */
+ PR_snprintf(ra->last_init_status, STATUS_LEN, "%d", ldaprc);
+ }
+ }
+}
+
+
+void
+agmt_set_update_in_progress (Repl_Agmt *ra, PRBool in_progress)
+{
+ PR_ASSERT(NULL != ra);
+ if (NULL != ra)
+ {
+ ra->update_in_progress = in_progress;
+ }
+}
+
+void
+agmt_inc_last_update_changecount (Repl_Agmt *ra, ReplicaId rid, int skipped)
+{
+ PR_ASSERT(NULL != ra);
+ if (NULL != ra)
+ {
+ int i;
+
+ for ( i = 0; i < ra->num_changecounters; i++ )
+ {
+ if ( ra->changecounters[i]->rid == rid )
+ break;
+ }
+
+ if ( i < ra->num_changecounters )
+ {
+ if ( skipped )
+ ra->changecounters[i]->num_skipped ++;
+ else
+ ra->changecounters[i]->num_replayed ++;
+ }
+ else
+ {
+ ra->num_changecounters ++;
+ ra->changecounters[i] = (struct changecounter*) slapi_ch_calloc(1, sizeof(struct changecounter));
+ ra->changecounters[i]->rid = rid;
+ if ( skipped )
+ ra->changecounters[i]->num_skipped = 1;
+ else
+ ra->changecounters[i]->num_replayed = 1;
+ }
+ }
+}
+
+void
+agmt_get_changecount_string (Repl_Agmt *ra, char *buf, int bufsize)
+{
+ char tmp_buf[32]; /* 5 digit RID, 10 digit each replayed and skipped */
+ int i;
+ int buflen = 0;
+
+ *buf = '\0';
+ if (NULL != ra)
+ {
+ for ( i = 0; i < ra->num_changecounters; i++ )
+ {
+ PR_snprintf (tmp_buf, sizeof(tmp_buf), "%u:%u/%u ",
+ ra->changecounters[i]->rid,
+ ra->changecounters[i]->num_replayed,
+ ra->changecounters[i]->num_skipped);
+ PR_snprintf (buf+buflen, bufsize-buflen, "%s", tmp_buf);
+ buflen += strlen (tmp_buf);
+ }
+ }
+}
+
+static int
+get_agmt_status(Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter,
+ int *returncode, char *returntext, void *arg)
+{
+ char *time_tmp = NULL;
+ char changecount_string[BUFSIZ];
+ Repl_Agmt *ra = (Repl_Agmt *)arg;
+
+ PR_ASSERT(NULL != ra);
+ if (NULL != ra)
+ {
+ PRBool reapActive = PR_FALSE;
+ Slapi_DN *replarea_sdn = NULL;
+ Object *repl_obj = NULL;
+
+ replarea_sdn = agmt_get_replarea(ra);
+ repl_obj = replica_get_replica_from_dn(replarea_sdn);
+ slapi_sdn_free(&replarea_sdn);
+ if (repl_obj) {
+ Replica *replica = (Replica*)object_get_data (repl_obj);
+ reapActive = replica_get_tombstone_reap_active(replica);
+ object_release(repl_obj);
+ }
+ slapi_entry_attr_set_int(e, "nsds5replicaReapActive", (int)reapActive);
+
+ /* these values persist in the dse.ldif file, so we delete them
+ here to avoid multi valued attributes */
+ slapi_entry_attr_delete(e, "nsds5replicaLastUpdateStart");
+ slapi_entry_attr_delete(e, "nsds5replicaLastUpdateEnd");
+ slapi_entry_attr_delete(e, "nsds5replicaChangesSentSinceStartup");
+ slapi_entry_attr_delete(e, "nsds5replicaLastUpdateStatus");
+ slapi_entry_attr_delete(e, "nsds5replicaUpdateInProgress");
+ slapi_entry_attr_delete(e, "nsds5replicaLastInitStart");
+ slapi_entry_attr_delete(e, "nsds5replicaLastInitStatus");
+ slapi_entry_attr_delete(e, "nsds5replicaLastInitEnd");
+
+ /* now, add the real values (singly) */
+ if (ra->last_update_start_time == 0)
+ {
+ slapi_entry_add_string(e, "nsds5replicaLastUpdateStart", "0");
+ }
+ else
+ {
+ time_tmp = format_genTime(ra->last_update_start_time);
+ slapi_entry_add_string(e, "nsds5replicaLastUpdateStart", time_tmp);
+ slapi_ch_free((void **)&time_tmp);
+ }
+ if (ra->last_update_end_time == 0)
+ {
+ slapi_entry_add_string(e, "nsds5replicaLastUpdateEnd", "0");
+ }
+ else
+ {
+ time_tmp = format_genTime(ra->last_update_end_time);
+ slapi_entry_add_string(e, "nsds5replicaLastUpdateEnd", time_tmp);
+ slapi_ch_free((void **)&time_tmp);
+ }
+ agmt_get_changecount_string (ra, changecount_string, sizeof (changecount_string) );
+ slapi_entry_add_string(e, "nsds5replicaChangesSentSinceStartup", changecount_string);
+ if (ra->last_update_status[0] == '\0')
+ {
+ slapi_entry_add_string(e, "nsds5replicaLastUpdateStatus", "0 No replication sessions started since server startup");
+ }
+ else
+ {
+ slapi_entry_add_string(e, "nsds5replicaLastUpdateStatus", ra->last_update_status);
+ }
+ slapi_entry_add_string(e, "nsds5replicaUpdateInProgress", ra->update_in_progress ? "TRUE" : "FALSE");
+ if (ra->last_init_start_time == 0)
+ {
+ slapi_entry_add_string(e, "nsds5replicaLastInitStart", "0");
+ }
+ else
+ {
+ time_tmp = format_genTime(ra->last_init_start_time);
+ slapi_entry_add_string(e, "nsds5replicaLastInitStart", time_tmp);
+ slapi_ch_free((void **)&time_tmp);
+ }
+ if (ra->last_init_end_time == 0)
+ {
+ slapi_entry_add_string(e, "nsds5replicaLastInitEnd", "0");
+ }
+ else
+ {
+ time_tmp = format_genTime(ra->last_init_end_time);
+ slapi_entry_add_string(e, "nsds5replicaLastInitEnd", time_tmp);
+ slapi_ch_free((void **)&time_tmp);
+ }
+ if (ra->last_init_status[0] != '\0')
+ {
+ slapi_entry_add_string(e, "nsds5replicaLastInitStatus", ra->last_init_status);
+ }
+ }
+ return SLAPI_DSE_CALLBACK_OK;
+}
+
+static void
+update_window_state_change_callback (void *arg, PRBool opened)
+{
+ Repl_Agmt *agmt = (Repl_Agmt*)arg;
+
+ PR_ASSERT (agmt);
+
+ if (opened)
+ {
+ prot_notify_window_opened (agmt->protocol);
+ }
+ else
+ {
+ prot_notify_window_closed (agmt->protocol);
+ }
+}
+
+ReplicaId
+agmt_get_consumer_rid ( Repl_Agmt *agmt, void *conn )
+{
+ if ( agmt->consumerRID <= 0 ) {
+
+ char mapping_tree_node[512];
+ struct berval **bvals = NULL;
+
+ PR_snprintf ( mapping_tree_node,
+ sizeof (mapping_tree_node),
+ "cn=replica,cn=\"%s\",cn=mapping tree,cn=config",
+ slapi_sdn_get_dn (agmt->replarea) );
+ conn_read_entry_attribute ( conn, mapping_tree_node, "nsDS5ReplicaID", &bvals );
+ if ( NULL != bvals && NULL != bvals[0] ) {
+ char *ridstr = slapi_ch_malloc( bvals[0]->bv_len + 1 );
+ memcpy ( ridstr, bvals[0]->bv_val, bvals[0]->bv_len );
+ ridstr[bvals[0]->bv_len] = '\0';
+ agmt->consumerRID = atoi (ridstr);
+ slapi_ch_free ( (void**) &ridstr );
+ ber_bvecfree ( bvals );
+ }
+ }
+
+ return agmt->consumerRID;
+}
diff --git a/ldap/servers/plugins/replication/repl5_agmtlist.c b/ldap/servers/plugins/replication/repl5_agmtlist.c
new file mode 100644
index 00000000..5c7213a6
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl5_agmtlist.c
@@ -0,0 +1,618 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* repl5_agmtlist.c */
+/*
+
+ Replication agreements are held in object set (objset.c).
+
+*/
+
+#include "repl5.h"
+
+#define AGMT_CONFIG_BASE "cn=mapping tree, cn=config"
+#define CONFIG_FILTER "(objectclass=nsds5replicationagreement)"
+
+PRCallOnceType once = {0};
+static Objset *agmt_set = NULL; /* The set of replication agreements */
+
+typedef struct agmt_wrapper {
+ Repl_Agmt *agmt;
+ void *handle;
+} agmt_wrapper;
+
+
+
+/*
+ * Find the replication agreement whose entry DN matches the given DN.
+ * Object is returned referenced, so be sure to release it when
+ * finished.
+ */
+Repl_Agmt *
+agmtlist_get_by_agmt_name(const Slapi_DN *agmt_name)
+{
+ Repl_Agmt *ra;
+ Object *ro;
+
+ for (ro = objset_first_obj(agmt_set); NULL != ro;
+ ro = objset_next_obj(agmt_set, ro))
+ {
+ ra = (Repl_Agmt *)object_get_data(ro);
+ if (agmt_matches_name(ra, agmt_name))
+ {
+ break;
+ }
+ }
+ return ra;
+}
+
+
+static int
+agmt_ptr_cmp(Object *ro, const void *arg)
+{
+ Repl_Agmt *ra;
+ Repl_Agmt *provided_ra = (Repl_Agmt *)arg;
+
+ ra = object_get_data(ro);
+
+ if (ra == provided_ra)
+ return 0;
+ else
+ return 1;
+}
+
+
+
+static int
+agmt_dn_cmp(Object *ro, const void *arg)
+{
+ Repl_Agmt *ra;
+ Slapi_DN *sdn = (Slapi_DN *)arg;
+
+ ra = object_get_data(ro);
+ return(slapi_sdn_compare(sdn, agmt_get_dn_byref(ra)));
+}
+
+void
+agmtlist_release_agmt(Repl_Agmt *ra)
+{
+ Object *ro;
+
+ PR_ASSERT(NULL != agmt_set);
+ PR_ASSERT(NULL != ra);
+
+ ro = objset_find(agmt_set, agmt_ptr_cmp, (const void *)ra);
+ if (NULL != ro)
+ {
+ /*
+ * Release twice - once for the reference we got when finding
+ * it, and once for the reference we got when we called
+ * agmtlist_get_*().
+ */
+ object_release(ro);
+ object_release(ro);
+ }
+}
+
+
+/*
+ * Note: when we add the new object, we have a reference to it. We hold
+ * on to this reference until the agreement is deleted (or until the
+ * server is shut down).
+ */
+static int
+add_new_agreement(Slapi_Entry *e)
+{
+ int rc = 0;
+ Repl_Agmt *ra = agmt_new_from_entry(e);
+ Slapi_DN *replarea_sdn = NULL;
+ Replica *replica = NULL;
+ Object *repl_obj = NULL;
+ Object *ro = NULL;
+
+ if (ra == NULL) return 0;
+
+ ro = object_new((void *)ra, agmt_delete);
+ objset_add_obj(agmt_set, ro);
+ object_release(ro); /* Object now owned by objset */
+
+ /* get the replica for this agreement */
+ replarea_sdn = agmt_get_replarea(ra);
+ repl_obj = replica_get_replica_from_dn(replarea_sdn);
+ slapi_sdn_free(&replarea_sdn);
+ if (repl_obj) {
+ replica = (Replica*)object_get_data (repl_obj);
+ }
+
+ rc = replica_start_agreement(replica, ra);
+
+ if (repl_obj) object_release(repl_obj);
+
+ return rc;
+}
+
+static int
+agmtlist_add_callback(Slapi_PBlock *pb, Slapi_Entry *e, Slapi_Entry *entryAfter,
+ int *returncode, char *returntext, void *arg)
+{
+ int rc;
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmt_add: begin\n");
+
+ rc = add_new_agreement(e);
+ if (0 != rc) {
+ char *dn;
+ slapi_pblock_get(pb, SLAPI_TARGET_DN, &dn);
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "agmtlist_add_callback: "
+ "Can't start agreement \"%s\"\n", dn);
+ *returncode = LDAP_UNWILLING_TO_PERFORM;
+ return SLAPI_DSE_CALLBACK_ERROR;
+ }
+ *returncode = LDAP_SUCCESS;
+ return SLAPI_DSE_CALLBACK_OK;
+}
+
+static int
+agmtlist_modify_callback(Slapi_PBlock *pb, Slapi_Entry *entryBefore, Slapi_Entry *e,
+ int *returncode, char *returntext, void *arg)
+{
+ int i;
+ char *dn;
+ Slapi_DN *sdn = NULL;
+ int start_initialize = 0, stop_initialize = 0, cancel_initialize = 0;
+ int update_the_schedule = 0; /* do we need to update the repl sched? */
+ Repl_Agmt *agmt = NULL;
+ LDAPMod **mods;
+ char buff [BUFSIZ];
+ char *errortext = returntext ? returntext : buff;
+ int rc = SLAPI_DSE_CALLBACK_OK;
+ Slapi_Operation *op;
+ void *identity;
+
+ *returncode = LDAP_SUCCESS;
+
+ /* just let internal operations originated from replication plugin to go through */
+ slapi_pblock_get (pb, SLAPI_OPERATION, &op);
+ slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &identity);
+
+ if (operation_is_flag_set(op, OP_FLAG_INTERNAL) &&
+ (identity == repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION)))
+ {
+ goto done;
+ }
+
+ slapi_pblock_get(pb, SLAPI_TARGET_DN, &dn);
+ sdn= slapi_sdn_new_dn_byref(dn);
+ agmt = agmtlist_get_by_agmt_name(sdn);
+ if (NULL == agmt)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "agmtlist_modify_callback: received "
+ "a modification for unknown replication agreement \"%s\"\n", dn);
+ goto done;
+ }
+
+ slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods);
+ for (i = 0; NULL != mods && NULL != mods[i]; i++)
+ {
+ if (slapi_attr_types_equivalent(mods[i]->mod_type, type_nsds5ReplicaInitialize))
+ {
+ /* we don't allow delete attribute operations unless it was issued by
+ the replication plugin - handled above */
+ if (mods[i]->mod_op & LDAP_MOD_DELETE)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: "
+ "deletion of %s attribute is not allowed\n", type_nsds5ReplicaInitialize);
+ *returncode = LDAP_UNWILLING_TO_PERFORM;
+ rc = SLAPI_DSE_CALLBACK_ERROR;
+ break;
+ }
+ else
+ {
+ char *val;
+
+ if (mods[i]->mod_bvalues && mods[i]->mod_bvalues[0])
+ val = slapi_berval_get_string_copy (mods[i]->mod_bvalues[0]);
+ else
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: "
+ "no value provided for %s attribute\n", type_nsds5ReplicaInitialize);
+ *returncode = LDAP_UNWILLING_TO_PERFORM;
+ rc = SLAPI_DSE_CALLBACK_ERROR;
+ break;
+ }
+
+ /* Start replica initialization */
+ if (val == NULL)
+ {
+ sprintf (errortext, "No value supplied for attr (%s)", mods[i]->mod_type);
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: %s\n",
+ errortext);
+ *returncode = LDAP_UNWILLING_TO_PERFORM;
+ rc = SLAPI_DSE_CALLBACK_ERROR;
+ break;
+ }
+
+ if (strcasecmp (val, "start") == 0)
+ {
+ start_initialize = 1;
+ }
+ else if (strcasecmp (val, "stop") == 0)
+ {
+ stop_initialize = 1;
+ }
+ else if (strcasecmp (val, "cancel") == 0)
+ {
+ cancel_initialize = 1;
+ }
+ else
+ {
+ sprintf (errortext, "Invalid value (%s) value supplied for attr (%s)",
+ val, mods[i]->mod_type);
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: %s\n",
+ errortext);
+ }
+
+ slapi_ch_free ((void**)&val);
+ }
+ }
+ else if (slapi_attr_types_equivalent(mods[i]->mod_type,
+ type_nsds5ReplicaUpdateSchedule))
+ {
+ /*
+ * Request to update the replication schedule. Set a flag so
+ * we know to update the schedule later.
+ */
+ update_the_schedule = 1;
+ }
+ else if (slapi_attr_types_equivalent(mods[i]->mod_type,
+ type_nsds5ReplicaCredentials))
+ {
+ /* New replica credentials */
+ if (agmt_set_credentials_from_entry(agmt, e) != 0)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: "
+ "failed to update credentials for agreement %s\n",
+ agmt_get_long_name(agmt));
+ *returncode = LDAP_OPERATIONS_ERROR;
+ rc = SLAPI_DSE_CALLBACK_ERROR;
+ }
+ }
+ else if (slapi_attr_types_equivalent(mods[i]->mod_type,
+ type_nsds5ReplicaTimeout))
+ {
+ /* New replica timeout */
+ if (agmt_set_timeout_from_entry(agmt, e) != 0)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: "
+ "failed to update timeout for agreement %s\n",
+ agmt_get_long_name(agmt));
+ *returncode = LDAP_OPERATIONS_ERROR;
+ rc = SLAPI_DSE_CALLBACK_ERROR;
+ }
+ }
+ else if (slapi_attr_types_equivalent(mods[i]->mod_type,
+ type_nsds5ReplicaBusyWaitTime))
+ {
+ /* New replica busywaittime */
+ if (agmt_set_busywaittime_from_entry(agmt, e) != 0)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: "
+ "failed to update busy wait time for agreement %s\n",
+ agmt_get_long_name(agmt));
+ *returncode = LDAP_OPERATIONS_ERROR;
+ rc = SLAPI_DSE_CALLBACK_ERROR;
+ }
+ }
+ else if (slapi_attr_types_equivalent(mods[i]->mod_type,
+ type_nsds5ReplicaSessionPauseTime))
+ {
+ /* New replica pausetime */
+ if (agmt_set_pausetime_from_entry(agmt, e) != 0)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: "
+ "failed to update session pause time for agreement %s\n",
+ agmt_get_long_name(agmt));
+ *returncode = LDAP_OPERATIONS_ERROR;
+ rc = SLAPI_DSE_CALLBACK_ERROR;
+ }
+ }
+ else if (slapi_attr_types_equivalent(mods[i]->mod_type,
+ type_nsds5ReplicaBindDN))
+ {
+ /* New replica Bind DN */
+ if (agmt_set_binddn_from_entry(agmt, e) != 0)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: "
+ "failed to update bind DN for agreement %s\n",
+ agmt_get_long_name(agmt));
+ *returncode = LDAP_OPERATIONS_ERROR;
+ rc = SLAPI_DSE_CALLBACK_ERROR;
+ }
+ }
+ else if (slapi_attr_types_equivalent(mods[i]->mod_type,
+ type_nsds5TransportInfo))
+ {
+ /* New Transport info */
+ if (agmt_set_transportinfo_from_entry(agmt, e) != 0)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: "
+ "failed to update transport info for agreement %s\n",
+ agmt_get_long_name(agmt));
+ *returncode = LDAP_OPERATIONS_ERROR;
+ rc = SLAPI_DSE_CALLBACK_ERROR;
+ }
+ }
+ else if (slapi_attr_types_equivalent(mods[i]->mod_type,
+ type_nsds5ReplicaBindMethod))
+ {
+ /* New replica bind method */
+ if (agmt_set_bind_method_from_entry(agmt, e) != 0)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: "
+ "failed to update bind method for agreement %s\n",
+ agmt_get_long_name(agmt));
+ *returncode = LDAP_OPERATIONS_ERROR;
+ rc = SLAPI_DSE_CALLBACK_ERROR;
+ }
+ }
+ else if (slapi_attr_types_equivalent(mods[i]->mod_type,
+ "nsds5debugreplicatimeout"))
+ {
+ char *val = slapi_entry_attr_get_charptr(e, "nsds5debugreplicatimeout");
+ repl5_set_debug_timeout(val);
+ slapi_ch_free_string(&val);
+ }
+ else if (strcasecmp (mods[i]->mod_type, "modifytimestamp") == 0 ||
+ strcasecmp (mods[i]->mod_type, "modifiersname") == 0 ||
+ strcasecmp (mods[i]->mod_type, "description") == 0)
+ {
+ /* ignore modifier's name and timestamp attributes and the description. */
+ continue;
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: "
+ "modification of %s attribute is not allowed\n", mods[i]->mod_type);
+ *returncode = LDAP_UNWILLING_TO_PERFORM;
+ rc = SLAPI_DSE_CALLBACK_ERROR;
+ break;
+ }
+ }
+
+ if (stop_initialize)
+ {
+ agmt_stop (agmt);
+ }
+ else if (start_initialize)
+ {
+ if (agmt_initialize_replica(agmt) != 0) {
+ /* The suffix is disabled */
+ agmt_set_last_init_status(agmt, 0, NSDS50_REPL_DISABLED, NULL);
+ }
+ }
+ else if (cancel_initialize)
+ {
+ agmt_replica_init_done(agmt);
+ }
+
+ if (update_the_schedule)
+ {
+ if (agmt_set_schedule_from_entry(agmt, e) != 0)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: "
+ "failed to update replication schedule for agreement %s\n",
+ agmt_get_long_name(agmt));
+ *returncode = LDAP_OPERATIONS_ERROR;
+ rc = SLAPI_DSE_CALLBACK_ERROR;
+ }
+ }
+
+done:
+ if (NULL != agmt)
+ {
+ agmtlist_release_agmt(agmt);
+ }
+
+ if (sdn)
+ slapi_sdn_free(&sdn);
+ return rc;
+}
+
+static int
+agmtlist_delete_callback(Slapi_PBlock *pb, Slapi_Entry *e, Slapi_Entry *entryAfter,
+ int *returncode, char *returntext, void *arg)
+{
+ Repl_Agmt *ra;
+ Object *ro;
+
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "agmt_delete: begin\n");
+ ro = objset_find(agmt_set, agmt_dn_cmp, (const void *)slapi_entry_get_sdn_const(e));
+ ra = (NULL == ro) ? NULL : (Repl_Agmt *)object_get_data(ro);
+ if (NULL == ra)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "agmtlist_delete: "
+ "Tried to delete replication agreement \"%s\", but no such "
+ "agreement was configured.\n", slapi_sdn_get_dn(slapi_entry_get_sdn_const(e)));
+ }
+ else
+ {
+ agmt_stop(ra);
+ object_release(ro); /* Release ref acquired in objset_find */
+ objset_remove_obj(agmt_set, ro); /* Releases a reference (should be final reference */
+ }
+ *returncode = LDAP_SUCCESS;
+ return SLAPI_DSE_CALLBACK_OK;
+}
+
+static int
+agmtlist_rename_callback(Slapi_PBlock *pb, Slapi_Entry *entryBefore, Slapi_Entry *e,
+ int *returncode, char *returntext, void *arg)
+{
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "agmt_rename: begin\n");
+
+ *returncode = LDAP_SUCCESS;
+ return SLAPI_DSE_CALLBACK_OK;
+}
+
+
+static int
+handle_agmt_search(Slapi_Entry *e, void *callback_data)
+{
+ int *agmtcount = (int *)callback_data;
+ int rc;
+
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "Found replication agreement named \"%s\".\n",
+ slapi_sdn_get_dn(slapi_entry_get_sdn(e)));
+ rc = add_new_agreement(e);
+ if (0 == rc)
+ {
+ (*agmtcount)++;
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "The replication "
+ "agreement named \"%s\" could not be correctly parsed. No "
+ "replication will occur with this replica.\n",
+ slapi_sdn_get_dn(slapi_entry_get_sdn(e)));
+ }
+
+ return rc;
+}
+
+
+static void
+agmtlist_objset_destructor(void **o)
+{
+ /* XXXggood Nothing to do, I think. */
+}
+
+
+int
+agmtlist_config_init()
+{
+ Slapi_PBlock *pb;
+ int agmtcount = 0;
+
+ agmt_set = objset_new(agmtlist_objset_destructor);
+
+ /* Register callbacks so we're informed about updates */
+ slapi_config_register_callback(SLAPI_OPERATION_ADD, DSE_FLAG_PREOP, AGMT_CONFIG_BASE,
+ LDAP_SCOPE_SUBTREE, CONFIG_FILTER, agmtlist_add_callback, NULL);
+ slapi_config_register_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_PREOP, AGMT_CONFIG_BASE,
+ LDAP_SCOPE_SUBTREE, CONFIG_FILTER, agmtlist_modify_callback, NULL);
+ slapi_config_register_callback(SLAPI_OPERATION_DELETE, DSE_FLAG_PREOP, AGMT_CONFIG_BASE,
+ LDAP_SCOPE_SUBTREE, CONFIG_FILTER, agmtlist_delete_callback, NULL);
+ slapi_config_register_callback(SLAPI_OPERATION_MODRDN, DSE_FLAG_PREOP, AGMT_CONFIG_BASE,
+ LDAP_SCOPE_SUBTREE, CONFIG_FILTER, agmtlist_rename_callback, NULL);
+
+ /* Search the DIT and find all the replication agreements */
+ pb = slapi_pblock_new();
+ slapi_search_internal_set_pb(pb, AGMT_CONFIG_BASE, LDAP_SCOPE_SUBTREE,
+ CONFIG_FILTER, NULL /* attrs */, 0 /* attrsonly */,
+ NULL, /* controls */ NULL /* uniqueid */,
+ repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION), 0 /* actions */);
+ slapi_search_internal_callback_pb(pb,
+ (void *)&agmtcount /* callback data */,
+ NULL /* result_callback */,
+ handle_agmt_search /* search entry cb */,
+ NULL /* referral callback */);
+ slapi_pblock_destroy(pb);
+
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_config_init: found %d replication agreements in DIT\n", agmtcount);
+
+ return 0;
+}
+
+
+
+void
+agmtlist_shutdown()
+{
+ Repl_Agmt *ra;
+ Object *ro;
+ Object *next_ro;
+
+ ro = objset_first_obj(agmt_set);
+ while (NULL != ro)
+ {
+ next_ro = objset_next_obj(agmt_set, ro);
+ ra = (Repl_Agmt *)object_get_data(ro);
+ agmt_stop(ra);
+ agmt_update_consumer_ruv (ra);
+ objset_remove_obj(agmt_set, ro);
+ ro = next_ro;
+ }
+ objset_delete(&agmt_set);
+ agmt_set = NULL;
+}
+
+
+
+/*
+ * Notify each replication agreement about an update.
+ */
+void
+agmtlist_notify_all(Slapi_PBlock *pb)
+{
+ Repl_Agmt *ra;
+ Object *ro;
+
+ if (NULL != agmt_set)
+ {
+ ro = objset_first_obj(agmt_set);
+ while (NULL != ro)
+ {
+ ra = (Repl_Agmt *)object_get_data(ro);
+ agmt_notify_change(ra, pb);
+ ro = objset_next_obj(agmt_set, ro);
+ }
+ }
+}
+
+Object* agmtlist_get_first_agreement_for_replica (Replica *r)
+{
+ return agmtlist_get_next_agreement_for_replica (r, NULL) ;
+}
+
+Object* agmtlist_get_next_agreement_for_replica (Replica *r, Object *prev)
+{
+ const Slapi_DN *replica_root;
+ Slapi_DN *agmt_root;
+ Object *obj;
+ Repl_Agmt *agmt;
+
+ if (r == NULL)
+ {
+ /* ONREPL - log error */
+ return NULL;
+ }
+
+ replica_root = replica_get_root(r);
+
+ if (prev)
+ obj = objset_next_obj(agmt_set, prev);
+ else
+ obj = objset_first_obj(agmt_set);
+
+ while (obj)
+ {
+ agmt = (Repl_Agmt*)object_get_data (obj);
+ PR_ASSERT (agmt);
+
+ agmt_root = agmt_get_replarea(agmt);
+ PR_ASSERT (agmt_root);
+
+ if (slapi_sdn_compare (replica_root, agmt_root) == 0)
+ {
+ slapi_sdn_free (&agmt_root);
+ return obj;
+ }
+
+ slapi_sdn_free (&agmt_root);
+ obj = objset_next_obj(agmt_set, obj);
+ }
+
+ return NULL;
+}
diff --git a/ldap/servers/plugins/replication/repl5_backoff.c b/ldap/servers/plugins/replication/repl5_backoff.c
new file mode 100644
index 00000000..d0a90878
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl5_backoff.c
@@ -0,0 +1,232 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* repl5_backoff.c */
+/*
+
+ The backoff object implements a backoff timer. The timer can operate
+ with a fixed interval, an expontially increasing interval, or a
+ random interval.
+
+ The caller creates a new backoff timer, specifying the backoff behavior
+ desired (fixed, exponential, or random), the initial backoff value,
+ and the maximum backoff interval. This does not start the timer - the
+ backoff_reset() function must be used to actually start the timer.
+
+ The backoff_reset() function takes an optional function that
+ will be called when the backoff time has expired, and a void *
+ that can be used to pass arguments into the callback function.
+
+ When the time expires, the callback function will be called. If no
+ callback function has been provided, the timer simply expires.
+ A timer does not recompute the next interval and begin timing until
+ the backoff_step() function is called. Therefore, callers that
+ do not install a callback function may use the timer by polling.
+ When a callback function is provided, the timer is typically reset
+ inside the callback function.
+
+*/
+
+#include "repl5.h"
+
+
+typedef struct backoff_timer {
+ int type;
+ int running;
+ slapi_eq_fn_t callback;
+ void *callback_arg;
+ time_t initial_interval;
+ time_t next_interval;
+ time_t max_interval;
+ time_t last_fire_time;
+ Slapi_Eq_Context pending_event;
+ PRLock *lock;
+
+} backoff_timer;
+
+/* Forward declarations */
+static PRIntervalTime random_interval_in_range(time_t lower_bound, time_t upper_bound);
+
+
+/*
+ Create a new backoff timer. The timer is initialized, but is not
+ started.
+ */
+Backoff_Timer *
+backoff_new(int timer_type, int initial_interval, int max_interval)
+{
+ Backoff_Timer *bt;
+
+ bt = (Backoff_Timer *)slapi_ch_calloc(1, sizeof(struct backoff_timer));
+ bt->type = timer_type;
+ bt->initial_interval = initial_interval;
+ bt->next_interval = bt->initial_interval;
+ bt->max_interval = max_interval;
+ bt->running = 0;
+ if ((bt->lock = PR_NewLock()) == NULL)
+ {
+ slapi_ch_free((void **)&bt);
+ }
+ return bt;
+}
+
+
+/*
+ * Reset and start the timer. Returns the time (as a time_t) when the
+ * time will next expire.
+ */
+time_t
+backoff_reset(Backoff_Timer *bt, slapi_eq_fn_t callback, void *callback_data)
+{
+ time_t return_value = 0UL;
+
+ PR_ASSERT(NULL != bt);
+ PR_ASSERT(NULL != callback);
+
+ PR_Lock(bt->lock);
+ bt->running = 1;
+ bt->callback = callback;
+ bt->callback_arg = callback_data;
+ /* Cancel any pending events in the event queue */
+ if (NULL != bt->pending_event)
+ {
+ slapi_eq_cancel(bt->pending_event);
+ bt->pending_event = NULL;
+ }
+ /* Compute the first fire time */
+ if (BACKOFF_RANDOM == bt->type)
+ {
+ bt->next_interval = random_interval_in_range(bt->initial_interval,
+ bt->max_interval);
+ }
+ else
+ {
+ bt->next_interval = bt->initial_interval;
+ }
+ /* Schedule the callback */
+ time(&bt->last_fire_time);
+ return_value = bt->last_fire_time + bt->next_interval;
+ bt->pending_event = slapi_eq_once(bt->callback, bt->callback_arg,
+ return_value);
+ PR_Unlock(bt->lock);
+ return return_value;
+}
+
+
+/*
+ Step the timer - compute the new backoff interval and start
+ counting. Note that the next expiration time is based on the
+ last timer expiration time, *not* the current time.
+
+ Returns the time (as a time_t) when the timer will next expire.
+ */
+time_t
+backoff_step(Backoff_Timer *bt)
+{
+ time_t return_value = 0UL;
+
+ PR_ASSERT(NULL != bt);
+
+ /* If the timer has never been reset, then return 0 */
+ PR_Lock(bt->lock);
+ if (bt->running)
+ {
+ time_t previous_interval = bt->next_interval;
+ switch (bt->type) {
+ case BACKOFF_FIXED:
+ /* Interval stays the same */
+ break;
+ case BACKOFF_EXPONENTIAL:
+ /* Interval doubles, up to a maximum */
+ if (bt->next_interval < bt->max_interval)
+ {
+ bt->next_interval *= 2;
+ if (bt->next_interval > bt->max_interval)
+ {
+ bt->next_interval = bt->max_interval;
+ }
+ }
+ break;
+ case BACKOFF_RANDOM:
+ /* Compute the new random interval time */
+ bt->next_interval = random_interval_in_range(bt->initial_interval,
+ bt->max_interval);
+ break;
+ }
+ /* Schedule the callback, if any */
+ bt->last_fire_time += previous_interval;
+ return_value = bt->last_fire_time + bt->next_interval;
+ bt->pending_event = slapi_eq_once(bt->callback, bt->callback_arg,
+ return_value);
+ }
+ PR_Unlock(bt->lock);
+ return return_value;
+}
+
+
+/*
+ * Return 1 if the backoff timer has expired, 0 otherwise.
+ */
+int
+backoff_expired(Backoff_Timer *bt, int margin)
+{
+ int return_value = 0;
+
+ PR_ASSERT(NULL != bt);
+ PR_Lock(bt->lock);
+ return_value = (current_time() >= (bt->last_fire_time + bt->next_interval + margin));
+ PR_Unlock(bt->lock);
+ return return_value;
+}
+
+
+/*
+ Destroy and deallocate a timer object
+ */
+void
+backoff_delete(Backoff_Timer **btp)
+{
+ Backoff_Timer *bt;
+
+ PR_ASSERT(NULL != btp && NULL != *btp);
+ bt = *btp;
+ PR_Lock(bt->lock);
+ /* Cancel any pending events in the event queue */
+ if (NULL != bt->pending_event)
+ {
+ slapi_eq_cancel(bt->pending_event);
+ }
+ PR_Unlock(bt->lock);
+ PR_DestroyLock(bt->lock);
+ slapi_ch_free((void **)btp);
+}
+
+
+/*
+ * Return the next fire time for the timer.
+ */
+time_t
+backoff_get_next_fire_time(Backoff_Timer *bt)
+{
+ time_t return_value;
+
+ PR_ASSERT(NULL != bt);
+ PR_Lock(bt->lock);
+ return_value = bt->last_fire_time + bt->next_interval;
+ PR_Unlock(bt->lock);
+ return return_value;
+}
+
+static PRIntervalTime
+random_interval_in_range(time_t lower_bound, time_t upper_bound)
+{
+ /*
+ * slapi_rand() provides some entropy from two or three system timer
+ * calls (depending on the platform) down in NSS. If more entropy is
+ * required, slapi_rand_r(unsigned int *seed) can be called instead.
+ */
+ return(lower_bound + (slapi_rand() % (upper_bound - lower_bound)));
+}
+
diff --git a/ldap/servers/plugins/replication/repl5_connection.c b/ldap/servers/plugins/replication/repl5_connection.c
new file mode 100644
index 00000000..a50c163a
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl5_connection.c
@@ -0,0 +1,1493 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+/* repl5_connection.c */
+/*
+
+ The connection object manages a connection to a single replication
+ consumer.
+
+XXXggood what to do on timeout? If we close connection, then we won't leave a
+replica locked. Seems like right thing to do.
+*/
+
+#include "repl5.h"
+#include "ldappr.h"
+
+typedef struct repl_connection
+{
+ char *hostname;
+ int port;
+ char *binddn;
+ int bindmethod;
+ int state;
+ int last_operation;
+ int last_ldap_error;
+ const char *status;
+ char *last_ldap_errmsg;
+ PRUint32 transport_flags;
+ LDAP *ld;
+ int supports_ldapv3; /* 1 if does, 0 if doesn't, -1 if not determined */
+ int supports_ds50_repl; /* 1 if does, 0 if doesn't, -1 if not determined */
+ int supports_ds40_repl; /* 1 if does, 0 if doesn't, -1 if not determined */
+ int linger_time; /* time in seconds to leave an idle connection open */
+ PRBool linger_active;
+ Slapi_Eq_Context *linger_event;
+ PRBool delete_after_linger;
+ int refcnt;
+ const Repl_Agmt *agmt;
+ PRLock *lock;
+ struct timeval timeout;
+ int flag_agmt_changed;
+ char *plain;
+} repl_connection;
+
+/* #define DEFAULT_LINGER_TIME (5 * 60) */ /* 5 minutes */
+#define DEFAULT_LINGER_TIME (60)
+
+/* Controls we add on every outbound operation */
+
+static LDAPControl manageDSAITControl = {LDAP_CONTROL_MANAGEDSAIT, {0, ""}, '\0'};
+static int attribute_string_value_present(LDAP *ld, LDAPMessage *entry,
+ const char *type, const char *value);
+static int bind_and_check_pwp(Repl_Connection *conn, char * binddn, char *password);
+static int do_simple_bind (Repl_Connection *conn, LDAP *ld, char * binddn, char *password);
+
+static int s_debug_timeout = 0;
+static int s_debug_level = 0;
+static Slapi_Eq_Context repl5_start_debug_timeout(int *setlevel);
+static void repl5_stop_debug_timeout(Slapi_Eq_Context eqctx, int *setlevel);
+static void repl5_debug_timeout_callback(time_t when, void *arg);
+#ifndef DSE_RETURNTEXT_SIZE
+#define SLAPI_DSE_RETURNTEXT_SIZE 512
+#endif
+
+#define STATE_CONNECTED 600
+#define STATE_DISCONNECTED 601
+
+#define STATUS_DISCONNECTED "disconnected"
+#define STATUS_CONNECTED "connected"
+#define STATUS_PROCESSING_ADD "processing add operation"
+#define STATUS_PROCESSING_DELETE "processing delete operation"
+#define STATUS_PROCESSING_MODIFY "processing modify operation"
+#define STATUS_PROCESSING_RENAME "processing rename operation"
+#define STATUS_PROCESSING_EXTENDED_OPERATION "processing extended operation"
+#define STATUS_LINGERING "lingering"
+#define STATUS_SHUTTING_DOWN "shutting down"
+#define STATUS_BINDING "connecting and binding"
+#define STATUS_SEARCHING "processing search operation"
+
+#define CONN_NO_OPERATION 0
+#define CONN_ADD 1
+#define CONN_DELETE 2
+#define CONN_MODIFY 3
+#define CONN_RENAME 4
+#define CONN_EXTENDED_OPERATION 5
+#define CONN_BIND 6
+#define CONN_INIT 7
+
+/* These are errors returned from ldap operations which should cause us to disconnect and
+ retry the connection later */
+#define IS_DISCONNECT_ERROR(rc) (rc == LDAP_SERVER_DOWN || rc == LDAP_CONNECT_ERROR || rc == LDAP_INVALID_CREDENTIALS || rc == LDAP_INAPPROPRIATE_AUTH || rc == LDAP_LOCAL_ERROR)
+
+/* Forward declarations */
+static void close_connection_internal(Repl_Connection *conn);
+
+/*
+ * Create a new conenction object. Returns a pointer to the object, or
+ * NULL if an error occurs.
+ */
+Repl_Connection *
+conn_new(Repl_Agmt *agmt)
+{
+ Repl_Connection *rpc;
+
+ rpc = (Repl_Connection *)slapi_ch_malloc(sizeof(repl_connection));
+ if ((rpc->lock = PR_NewLock()) == NULL)
+ {
+ goto loser;
+ }
+ rpc->hostname = agmt_get_hostname(agmt);
+ rpc->port = agmt_get_port(agmt);
+ rpc->binddn = agmt_get_binddn(agmt);
+ rpc->bindmethod = agmt_get_bindmethod(agmt);
+ rpc->transport_flags = agmt_get_transport_flags(agmt);
+ rpc->ld = NULL;
+ rpc->state = STATE_DISCONNECTED;
+ rpc->last_operation = CONN_NO_OPERATION;
+ rpc->last_ldap_error = LDAP_SUCCESS;
+ rpc->last_ldap_errmsg = NULL;
+ rpc->supports_ldapv3 = -1;
+ rpc->supports_ds40_repl = -1;
+ rpc->supports_ds50_repl = -1;
+ rpc->linger_active = PR_FALSE;
+ rpc->delete_after_linger = PR_FALSE;
+ rpc->linger_event = NULL;
+ rpc->linger_time = DEFAULT_LINGER_TIME;
+ rpc->status = STATUS_DISCONNECTED;
+ rpc->agmt = agmt;
+ rpc->refcnt = 1;
+ rpc->timeout.tv_sec = agmt_get_timeout(agmt);
+ rpc->timeout.tv_usec = 0;
+ rpc->flag_agmt_changed = 0;
+ rpc->plain = NULL;
+ return rpc;
+loser:
+ conn_delete(rpc);
+ return NULL;
+}
+
+
+/*
+ * Return PR_TRUE if the connection is in the connected state
+ */
+static PRBool
+conn_connected(Repl_Connection *conn)
+{
+ PRBool return_value;
+ PR_Lock(conn->lock);
+ return_value = STATE_CONNECTED == conn->state;
+ PR_Unlock(conn->lock);
+ return return_value;
+}
+
+
+/*
+ * Destroy a connection object.
+ */
+static void
+conn_delete_internal(Repl_Connection *conn)
+{
+ PR_ASSERT(NULL != conn);
+ close_connection_internal(conn);
+ /* slapi_ch_free accepts NULL pointer */
+ slapi_ch_free((void **)&conn->hostname);
+ slapi_ch_free((void **)&conn->binddn);
+ slapi_ch_free((void **)&conn->plain);
+}
+
+/*
+ * Destroy a connection. It is an error to use the connection object
+ * after conn_delete() has been called.
+ */
+void
+conn_delete(Repl_Connection *conn)
+{
+ PRBool destroy_it = PR_FALSE;
+
+ PR_ASSERT(NULL != conn);
+ PR_Lock(conn->lock);
+ if (conn->linger_active)
+ {
+ if (slapi_eq_cancel(conn->linger_event) == 1)
+ {
+ /* Event was found and cancelled. Destroy the connection object. */
+ PR_Unlock(conn->lock);
+ destroy_it = PR_TRUE;
+ }
+ else
+ {
+ /*
+ * The event wasn't found, but we think it's still active.
+ * That means an event is in the process of being fired
+ * off, so arrange for the event to destroy the object .
+ */
+ conn->delete_after_linger = PR_TRUE;
+ PR_Unlock(conn->lock);
+ }
+ }
+ if (destroy_it)
+ {
+ conn_delete_internal(conn);
+ }
+}
+
+
+/*
+ * Return the last operation type processed by the connection
+ * object, and the LDAP error encountered.
+ */
+void
+conn_get_error(Repl_Connection *conn, int *operation, int *error)
+{
+ PR_Lock(conn->lock);
+ *operation = conn->last_operation;
+ *error = conn->last_ldap_error;
+ PR_Unlock(conn->lock);
+}
+
+
+/*
+ * Common code to send an LDAPv3 operation and collect the result.
+ * Return values:
+ * CONN_OPERATION_SUCCESS - the operation succeeded
+ * CONN_OPERATION_FAILED - the operation was sent to the consumer
+ * and failed. Use conn_get_error() to determine the LDAP error
+ * code.
+ * CONN_NOT_CONNECTED - no connection is active. The caller should
+ * use conn_connect() to connect to the replica and bind, then should
+ * reacquire the replica (if needed).
+ * CONN_BUSY - the server is busy with previous requests, must wait for a while
+ * before retrying
+ */
+static ConnResult
+perform_operation(Repl_Connection *conn, int optype, const char *dn,
+ LDAPMod **attrs, const char *newrdn, const char *newparent,
+ int deleteoldrdn, LDAPControl *update_control,
+ const char *extop_oid, struct berval *extop_payload, char **retoidp,
+ struct berval **retdatap, LDAPControl ***returned_controls)
+{
+ int rc;
+ ConnResult return_value;
+ LDAPControl *server_controls[3];
+ LDAPControl **loc_returned_controls;
+ const char *op_string = NULL;
+ const char *extra_op_string = NULL;
+
+ server_controls[0] = &manageDSAITControl;
+ server_controls[1] = update_control;
+ server_controls[2] = NULL;
+
+ if (conn_connected(conn))
+ {
+ int msgid;
+
+ conn->last_operation = optype;
+ switch (optype)
+ {
+ case CONN_ADD:
+ conn->status = STATUS_PROCESSING_ADD;
+ op_string = "add";
+ rc = ldap_add_ext(conn->ld, dn, attrs, server_controls,
+ NULL /* clientctls */, &msgid);
+ break;
+ case CONN_MODIFY:
+ conn->status = STATUS_PROCESSING_MODIFY;
+ op_string = "modify";
+ rc = ldap_modify_ext(conn->ld, dn, attrs, server_controls,
+ NULL /* clientctls */, &msgid);
+ break;
+ case CONN_DELETE:
+ conn->status = STATUS_PROCESSING_DELETE;
+ op_string = "delete";
+ rc = ldap_delete_ext(conn->ld, dn, server_controls,
+ NULL /* clientctls */, &msgid);
+ break;
+ case CONN_RENAME:
+ conn->status = STATUS_PROCESSING_RENAME;
+ op_string = "rename";
+ rc = ldap_rename(conn->ld, dn, newrdn, newparent, deleteoldrdn,
+ server_controls, NULL /* clientctls */, &msgid);
+ break;
+ case CONN_EXTENDED_OPERATION:
+ conn->status = STATUS_PROCESSING_EXTENDED_OPERATION;
+ op_string = "extended";
+ extra_op_string = extop_oid;
+ rc = ldap_extended_operation(conn->ld, extop_oid, extop_payload,
+ server_controls, NULL /* clientctls */, &msgid);
+ }
+ if (LDAP_SUCCESS == rc)
+ {
+ LDAPMessage *res = NULL;
+ int setlevel = 0;
+ Slapi_Eq_Context eqctx = repl5_start_debug_timeout(&setlevel);
+
+ rc = ldap_result(conn->ld, msgid, 1, &conn->timeout, &res);
+ repl5_stop_debug_timeout(eqctx, &setlevel);
+ if (0 == rc)
+ {
+ /* Timeout */
+ rc = ldap_get_lderrno(conn->ld, NULL, NULL);
+ conn->last_ldap_error = LDAP_TIMEOUT;
+ return_value = CONN_TIMEOUT;
+ }
+ else if (-1 == rc)
+ {
+ /* Error */
+ char *s = NULL;
+
+ rc = ldap_get_lderrno(conn->ld, NULL, &s);
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Received error %d: %s for %s operation\n",
+ agmt_get_long_name(conn->agmt),
+ rc, s ? s : "NULL",
+ op_string ? op_string : "NULL");
+ conn->last_ldap_error = rc;
+ /* some errors will require a disconnect and retry the connection
+ later */
+ if (IS_DISCONNECT_ERROR(rc))
+ {
+ conn_disconnect(conn);
+ return_value = CONN_NOT_CONNECTED;
+ }
+ else
+ {
+ conn->status = STATUS_CONNECTED;
+ return_value = CONN_OPERATION_FAILED;
+ }
+ }
+ else
+ {
+ int err;
+ char *errmsg = NULL;
+ char **referrals = NULL;
+ char *matched = NULL;
+
+ rc = ldap_parse_result(conn->ld, res, &err, &matched,
+ &errmsg, &referrals, &loc_returned_controls,
+ 0 /* Don't free the result */);
+ if (IS_DISCONNECT_ERROR(rc))
+ {
+ conn->last_ldap_error = rc;
+ conn_disconnect(conn);
+ return_value = CONN_NOT_CONNECTED;
+ }
+ else if (IS_DISCONNECT_ERROR(err))
+ {
+ conn->last_ldap_error = err;
+ conn_disconnect(conn);
+ return_value = CONN_NOT_CONNECTED;
+ }
+ /* Got a result */
+ else if (CONN_EXTENDED_OPERATION == optype)
+ {
+ if ((rc == LDAP_SUCCESS) && (err == LDAP_BUSY))
+ return_value = CONN_BUSY;
+ else {
+ if (rc == LDAP_SUCCESS) {
+ rc = ldap_parse_extended_result(conn->ld, res, retoidp,
+ retdatap, 0 /* Don't Free it */);
+ }
+ conn->last_ldap_error = rc;
+ return_value = (LDAP_SUCCESS == conn->last_ldap_error ?
+ CONN_OPERATION_SUCCESS : CONN_OPERATION_FAILED);
+ }
+ }
+ else /* regular operation, result returned */
+ {
+ if (NULL != returned_controls)
+ {
+ *returned_controls = loc_returned_controls;
+ }
+ if (LDAP_SUCCESS != rc)
+ {
+ conn->last_ldap_error = rc;
+ }
+ else
+ {
+ conn->last_ldap_error = err;
+ }
+ return_value = LDAP_SUCCESS == conn->last_ldap_error ? CONN_OPERATION_SUCCESS : CONN_OPERATION_FAILED;
+ }
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: Received result code %d for %s operation %s%s\n",
+ agmt_get_long_name(conn->agmt),
+ conn->last_ldap_error,
+ op_string == NULL ? "" : op_string,
+ extra_op_string == NULL ? "" : extra_op_string,
+ extra_op_string == NULL ? "" : " ");
+ /*
+ * XXXggood do I need to free matched, referrals,
+ * anything else? Or can I pass NULL for the args
+ * I'm not interested in?
+ */
+ /* Good question! Meanwhile, as RTM aproaches, let's free them... */
+ slapi_ch_free((void **) &errmsg);
+ slapi_ch_free((void **) &matched);
+ charray_free(referrals);
+ conn->status = STATUS_CONNECTED;
+ }
+ if (res) ldap_msgfree(res);
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Failed to send %s operation: LDAP error %d (%s)\n",
+ agmt_get_long_name(conn->agmt),
+ op_string ? op_string : "NULL", rc, ldap_err2string(rc));
+ conn->last_ldap_error = rc;
+ if (IS_DISCONNECT_ERROR(rc))
+ {
+ conn_disconnect(conn);
+ return_value = CONN_NOT_CONNECTED;
+ }
+ else
+ {
+ conn->status = STATUS_CONNECTED;
+ return_value = CONN_OPERATION_FAILED;
+ }
+ }
+ }
+ else
+ {
+ /* conn->last_ldap_error has been set to a more specific value
+ * in conn_connected()
+ * conn->last_ldap_error = LDAP_SERVER_DOWN;
+ */
+ return_value = CONN_NOT_CONNECTED;
+ }
+ return return_value;
+}
+
+/*
+ * Send an LDAP add operation.
+ */
+ConnResult
+conn_send_add(Repl_Connection *conn, const char *dn, LDAPMod **attrs,
+ LDAPControl *update_control, LDAPControl ***returned_controls)
+{
+ return perform_operation(conn, CONN_ADD, dn, attrs, NULL /* newrdn */,
+ NULL /* newparent */, 0 /* deleteoldrdn */, update_control,
+ NULL /* extop OID */, NULL /* extop payload */, NULL /* retoidp */,
+ NULL /* retdatap */, returned_controls);
+}
+
+
+/*
+ * Send an LDAP delete operation.
+ */
+ConnResult
+conn_send_delete(Repl_Connection *conn, const char *dn,
+ LDAPControl *update_control, LDAPControl ***returned_controls)
+{
+ return perform_operation(conn, CONN_DELETE, dn, NULL /* attrs */,
+ NULL /* newrdn */, NULL /* newparent */, 0 /* deleteoldrdn */,
+ update_control, NULL /* extop OID */, NULL /* extop payload */,
+ NULL /* retoidp */, NULL /* retdatap */, returned_controls);
+}
+
+
+/*
+ * Send an LDAP modify operation.
+ */
+ConnResult
+conn_send_modify(Repl_Connection *conn, const char *dn, LDAPMod **mods,
+ LDAPControl *update_control, LDAPControl ***returned_controls)
+{
+ return perform_operation(conn, CONN_MODIFY, dn, mods, NULL /* newrdn */,
+ NULL /* newparent */, 0 /* deleteoldrdn */, update_control,
+ NULL /* extop OID */, NULL /* extop payload */, NULL /* retoidp */,
+ NULL /* retdatap */, returned_controls);
+}
+
+/*
+ * Send an LDAP moddn operation.
+ */
+ConnResult
+conn_send_rename(Repl_Connection *conn, const char *dn,
+ const char *newrdn, const char *newparent, int deleteoldrdn,
+ LDAPControl *update_control, LDAPControl ***returned_controls)
+{
+ return perform_operation(conn, CONN_RENAME, dn, NULL /* attrs */,
+ newrdn, newparent, deleteoldrdn, update_control,
+ NULL /* extop OID */, NULL /* extop payload */, NULL /* retoidp */,
+ NULL /* retdatap */, returned_controls);
+}
+
+
+/*
+ * Send an LDAP extended operation.
+ */
+ConnResult
+conn_send_extended_operation(Repl_Connection *conn, const char *extop_oid,
+ struct berval *payload, char **retoidp, struct berval **retdatap,
+ LDAPControl *update_control, LDAPControl ***returned_controls)
+{
+ return perform_operation(conn, CONN_EXTENDED_OPERATION, NULL /* dn */, NULL /* attrs */,
+ NULL /* newrdn */, NULL /* newparent */, 0 /* deleteoldrdn */,
+ update_control, extop_oid, payload, retoidp, retdatap,
+ returned_controls);
+}
+
+
+/*
+ * Synchronously read an entry and return a specific attribute's values.
+ * Returns CONN_OPERATION_SUCCESS if successful. Returns
+ * CONN_OPERATION_FAILED if the operation was sent but an LDAP error
+ * occurred (conn->last_ldap_error is set in this case), and
+ * CONN_NOT_CONNECTED if no connection was active.
+ *
+ * The caller must free the returned_bvals.
+ */
+ConnResult
+conn_read_entry_attribute(Repl_Connection *conn, const char *dn,
+ char *type, struct berval ***returned_bvals)
+{
+ ConnResult return_value;
+ int ldap_rc;
+ LDAPControl *server_controls[2];
+ LDAPMessage *res = NULL;
+ char *attrs[2];
+
+ PR_ASSERT(NULL != type);
+ if (conn_connected(conn))
+ {
+ server_controls[0] = &manageDSAITControl;
+ server_controls[1] = NULL;
+ attrs[0] = type;
+ attrs[1] = NULL;
+ ldap_rc = ldap_search_ext_s(conn->ld, dn, LDAP_SCOPE_BASE,
+ "(objectclass=*)", attrs, 0 /* attrsonly */,
+ server_controls, NULL /* client controls */,
+ &conn->timeout, 0 /* sizelimit */, &res);
+ if (LDAP_SUCCESS == ldap_rc)
+ {
+ LDAPMessage *entry = ldap_first_entry(conn->ld, res);
+ if (NULL != entry)
+ {
+ *returned_bvals = ldap_get_values_len(conn->ld, entry, type);
+ }
+ return_value = CONN_OPERATION_SUCCESS;
+ }
+ else if (IS_DISCONNECT_ERROR(ldap_rc))
+ {
+ conn_disconnect(conn);
+ return_value = CONN_NOT_CONNECTED;
+ }
+ else
+ {
+ return_value = CONN_OPERATION_FAILED;
+ }
+ conn->last_ldap_error = ldap_rc;
+ if (NULL != res)
+ {
+ ldap_msgfree(res);
+ res = NULL;
+ }
+ }
+ else
+ {
+ return_value = CONN_NOT_CONNECTED;
+ }
+ return return_value;
+}
+
+
+/*
+ * Return an pointer to a string describing the connection's status.
+*/
+
+const char *
+conn_get_status(Repl_Connection *conn)
+{
+ return conn->status;
+}
+
+
+
+/*
+ * Cancel any outstanding linger timer. Should be called when
+ * a replication session is beginning.
+ */
+void
+conn_cancel_linger(Repl_Connection *conn)
+{
+ PR_ASSERT(NULL != conn);
+ PR_Lock(conn->lock);
+ if (conn->linger_active)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: Cancelling linger on the connection\n",
+ agmt_get_long_name(conn->agmt));
+ conn->linger_active = PR_FALSE;
+ if (slapi_eq_cancel(conn->linger_event) == 1)
+ {
+ conn->refcnt--;
+ }
+ conn->linger_event = NULL;
+ conn->status = STATUS_CONNECTED;
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: No linger to cancel on the connection\n",
+ agmt_get_long_name(conn->agmt));
+ }
+ PR_Unlock(conn->lock);
+}
+
+
+/*
+ * Called when our linger timeout timer expires. This means
+ * we should check to see if perhaps the connection's become
+ * active again, in which case we do nothing. Otherwise,
+ * we close the connection.
+ */
+static void
+linger_timeout(time_t event_time, void *arg)
+{
+ PRBool delete_now;
+ Repl_Connection *conn = (Repl_Connection *)arg;
+
+ PR_ASSERT(NULL != conn);
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: Linger timeout has expired on the connection\n",
+ agmt_get_long_name(conn->agmt));
+ PR_Lock(conn->lock);
+ if (conn->linger_active)
+ {
+ conn->linger_active = PR_FALSE;
+ conn->linger_event = NULL;
+ close_connection_internal(conn);
+ }
+ delete_now = conn->delete_after_linger;
+ PR_Unlock(conn->lock);
+ if (delete_now)
+ {
+ conn_delete_internal(conn);
+ }
+}
+
+
+/*
+ * Indicate that a session is ending. The linger timer starts when
+ * this function is called.
+ */
+void
+conn_start_linger(Repl_Connection *conn)
+{
+ time_t now;
+
+ PR_ASSERT(NULL != conn);
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: Beginning linger on the connection\n",
+ agmt_get_long_name(conn->agmt));
+ if (!conn_connected(conn))
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: No linger on the closed conn\n",
+ agmt_get_long_name(conn->agmt));
+ return;
+ }
+ time(&now);
+ PR_Lock(conn->lock);
+ if (conn->linger_active)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: Linger already active on the connection\n",
+ agmt_get_long_name(conn->agmt));
+ }
+ else
+ {
+ conn->linger_active = PR_TRUE;
+ conn->linger_event = slapi_eq_once(linger_timeout, conn, now + conn->linger_time);
+ conn->status = STATUS_LINGERING;
+ }
+ PR_Unlock(conn->lock);
+}
+
+
+
+/*
+ * If no connection is currently active, opens a connection and binds to
+ * the remote server. If a connection is open (e.g. lingering) then
+ * this is a no-op.
+ *
+ * Returns CONN_OPERATION_SUCCESS on success, or CONN_OPERATION_FAILED
+ * on failure. Sets conn->last_ldap_error and conn->last_operation;
+ */
+ConnResult
+conn_connect(Repl_Connection *conn)
+{
+ int ldap_rc;
+ int optdata;
+ int secure = 0;
+ char* binddn = NULL;
+ struct berval *creds;
+ ConnResult return_value = CONN_OPERATION_SUCCESS;
+ int pw_ret = 1;
+
+ /** Connection already open just return SUCCESS **/
+ if(conn->state == STATE_CONNECTED) return return_value;
+
+ PR_Lock(conn->lock);
+ if (conn->flag_agmt_changed) {
+ /* So far we cannot change Hostname and Port */
+ /* slapi_ch_free((void **)&conn->hostname); */
+ /* conn->hostname = agmt_get_hostname(conn->agmt); */
+ /* conn->port = agmt_get_port(conn->agmt); */
+ slapi_ch_free((void **)&conn->binddn);
+ conn->binddn = agmt_get_binddn(conn->agmt);
+ conn->bindmethod = agmt_get_bindmethod(conn->agmt);
+ conn->transport_flags = agmt_get_transport_flags(conn->agmt);
+ conn->timeout.tv_sec = agmt_get_timeout(conn->agmt);
+ conn->flag_agmt_changed = 0;
+ slapi_ch_free((void **)&conn->plain);
+ }
+ PR_Unlock(conn->lock);
+
+ creds = agmt_get_credentials(conn->agmt);
+
+ if (conn->plain == NULL) {
+
+ char *plain = NULL;
+
+ /* kexcoff: for reversible encryption */
+ /* We need to test the return code of pw_rever_decode in order to decide
+ * if a free for plain will be needed (pw_ret == 0) or not (pw_ret != 0) */
+ pw_ret = pw_rever_decode(creds->bv_val, &plain, type_nsds5ReplicaCredentials);
+ /* Pb occured in decryption: stop now, binding will fail */
+ if ( pw_ret == -1 )
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Decoding of the credentials failed.\n",
+ agmt_get_long_name(conn->agmt));
+
+ return_value = CONN_OPERATION_FAILED;
+ conn->last_ldap_error = LDAP_INVALID_CREDENTIALS;
+ conn->state = STATE_DISCONNECTED;
+ return (return_value);
+ } /* Else, does not mean that the plain is correct, only means the we had no internal
+ decoding pb */
+ conn->plain = slapi_ch_strdup (plain);
+ if (!pw_ret) slapi_ch_free((void**)&plain);
+ }
+
+
+ /* ugaston: if SSL has been selected in the replication agreement, SSL client
+ * initialisation should be done before ever trying to open any connection at all.
+ */
+ if (conn->transport_flags == TRANSPORT_FLAG_TLS)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Replication secured by StartTLS not currently supported\n",
+ agmt_get_long_name(conn->agmt));
+
+ return_value = CONN_OPERATION_FAILED;
+ conn->last_ldap_error = LDAP_STRONG_AUTH_NOT_SUPPORTED;
+ conn->state = STATE_DISCONNECTED;
+ } else if(conn->transport_flags == TRANSPORT_FLAG_SSL)
+ {
+
+ /** Make sure the SSL Library has been initialized before anything else **/
+ if(slapd_security_library_is_initialized() != 1)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: SSL Not Initialized, Replication over SSL FAILED\n",
+ agmt_get_long_name(conn->agmt));
+ conn->last_ldap_error = LDAP_INAPPROPRIATE_AUTH;
+ conn->last_operation = CONN_INIT;
+ ber_bvfree(creds);
+ creds = NULL;
+ return CONN_SSL_NOT_ENABLED;
+ } else
+ {
+ secure = 1;
+ }
+ }
+
+ if (return_value == CONN_OPERATION_SUCCESS) {
+ int io_timeout_ms;
+ /* Now we initialize the LDAP Structure and set options */
+
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: Trying %s slapi_ldap_init\n",
+ agmt_get_long_name(conn->agmt),
+ secure ? "secure" : "non-secure");
+
+ conn->ld = slapi_ldap_init(conn->hostname, conn->port, secure, 0);
+ if (NULL == conn->ld)
+ {
+ return_value = CONN_OPERATION_FAILED;
+ conn->state = STATE_DISCONNECTED;
+ conn->last_operation = CONN_INIT;
+ conn->last_ldap_error = LDAP_LOCAL_ERROR;
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Failed to establish %sconnection to the consumer\n",
+ agmt_get_long_name(conn->agmt),
+ secure ? "secure " : "");
+ ber_bvfree(creds);
+ creds = NULL;
+ return return_value;
+ }
+
+ /* slapi_ch_strdup is OK with NULL strings */
+ binddn = slapi_ch_strdup(conn->binddn);
+
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: binddn = %s, passwd = %s\n",
+ agmt_get_long_name(conn->agmt),
+ binddn?binddn:"NULL", creds->bv_val?creds->bv_val:"NULL");
+
+ /* Set some options for the connection. */
+ optdata = LDAP_DEREF_NEVER; /* Don't dereference aliases */
+ ldap_set_option(conn->ld, LDAP_OPT_DEREF, &optdata);
+
+ optdata = LDAP_VERSION3; /* We need LDAP version 3 */
+ ldap_set_option(conn->ld, LDAP_OPT_PROTOCOL_VERSION, &optdata);
+
+ /* Don't chase any referrals (although we shouldn't get any) */
+ ldap_set_option(conn->ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF);
+
+ /* override the default timeout with the specified timeout */
+ io_timeout_ms = conn->timeout.tv_sec * 1000 + conn->timeout.tv_usec / 1000;
+ prldap_set_session_option(conn->ld, NULL, PRLDAP_OPT_IO_MAX_TIMEOUT,
+ io_timeout_ms);
+
+ /* We've got an ld. Now bind to the server. */
+ conn->last_operation = CONN_BIND;
+
+ }
+
+ if ( bind_and_check_pwp(conn, binddn, conn->plain) == CONN_OPERATION_FAILED )
+ {
+ conn->last_ldap_error = ldap_get_lderrno (conn->ld, NULL, NULL);
+ conn->state = STATE_DISCONNECTED;
+ return_value = CONN_OPERATION_FAILED;
+ }
+ else
+ {
+ conn->last_ldap_error = ldap_rc = LDAP_SUCCESS;
+ conn->state = STATE_CONNECTED;
+ return_value = CONN_OPERATION_SUCCESS;
+ }
+
+
+ ber_bvfree(creds);
+ creds = NULL;
+
+ slapi_ch_free((void**)&binddn);
+
+ if(return_value == CONN_OPERATION_FAILED)
+ {
+ close_connection_internal(conn);
+ } else
+ {
+ conn->last_ldap_error = ldap_rc = LDAP_SUCCESS;
+ conn->state = STATE_CONNECTED;
+ }
+
+ return return_value;
+}
+
+
+static void
+close_connection_internal(Repl_Connection *conn)
+{
+ if (NULL != conn->ld)
+ {
+ /* Since we call slapi_ldap_init,
+ we must call slapi_ldap_unbind */
+ slapi_ldap_unbind(conn->ld);
+ }
+ conn->ld = NULL;
+ conn->state = STATE_DISCONNECTED;
+ conn->status = STATUS_DISCONNECTED;
+ conn->supports_ds50_repl = -1;
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: Disconnected from the consumer\n", agmt_get_long_name(conn->agmt));
+}
+
+void
+conn_disconnect(Repl_Connection *conn)
+{
+ PR_ASSERT(NULL != conn);
+ PR_Lock(conn->lock);
+ close_connection_internal(conn);
+ PR_Unlock(conn->lock);
+}
+
+
+/*
+ * Determine if the remote replica supports DS 5.0 replication.
+ * Return codes:
+ * CONN_SUPPORTS_DS5_REPL - the remote replica suport DS5 replication
+ * CONN_DOES_NOT_SUPPORT_DS5_REPL - the remote replica does not
+ * support DS5 replication.
+ * CONN_OPERATION_FAILED - it could not be determined if the remote
+ * replica supports DS5 replication.
+ * CONN_NOT_CONNECTED - no connection was active.
+ */
+ConnResult
+conn_replica_supports_ds5_repl(Repl_Connection *conn)
+{
+ ConnResult return_value;
+ int ldap_rc;
+
+ if (conn_connected(conn))
+ {
+ if (conn->supports_ds50_repl == -1) {
+ LDAPMessage *res = NULL;
+ LDAPMessage *entry = NULL;
+ char *attrs[] = {"supportedcontrol", "supportedextension", NULL};
+
+ conn->status = STATUS_SEARCHING;
+ ldap_rc = ldap_search_ext_s(conn->ld, "", LDAP_SCOPE_BASE,
+ "(objectclass=*)", attrs, 0 /* attrsonly */,
+ NULL /* server controls */, NULL /* client controls */,
+ &conn->timeout, LDAP_NO_LIMIT, &res);
+ if (LDAP_SUCCESS == ldap_rc)
+ {
+ conn->supports_ds50_repl = 0;
+ entry = ldap_first_entry(conn->ld, res);
+ if (!attribute_string_value_present(conn->ld, entry, "supportedcontrol", REPL_NSDS50_UPDATE_INFO_CONTROL_OID))
+ {
+ return_value = CONN_DOES_NOT_SUPPORT_DS5_REPL;
+ }
+ else if (!attribute_string_value_present(conn->ld, entry, "supportedextension", REPL_START_NSDS50_REPLICATION_REQUEST_OID))
+ {
+ return_value = CONN_DOES_NOT_SUPPORT_DS5_REPL;
+ }
+ else if (!attribute_string_value_present(conn->ld, entry, "supportedextension", REPL_END_NSDS50_REPLICATION_REQUEST_OID))
+ {
+ return_value = CONN_DOES_NOT_SUPPORT_DS5_REPL;
+ }
+ else if (!attribute_string_value_present(conn->ld, entry, "supportedextension", REPL_NSDS50_REPLICATION_ENTRY_REQUEST_OID))
+ {
+ return_value = CONN_DOES_NOT_SUPPORT_DS5_REPL;
+ }
+ else if (!attribute_string_value_present(conn->ld, entry, "supportedextension", REPL_NSDS50_REPLICATION_RESPONSE_OID))
+ {
+ return_value = CONN_DOES_NOT_SUPPORT_DS5_REPL;
+ }
+ else
+ {
+ conn->supports_ds50_repl = 1;
+ return_value = CONN_SUPPORTS_DS5_REPL;
+ }
+ }
+ else
+ {
+ if (IS_DISCONNECT_ERROR(ldap_rc))
+ {
+ conn->last_ldap_error = ldap_rc; /* specific reason */
+ conn_disconnect(conn);
+ return_value = CONN_NOT_CONNECTED;
+ }
+ else
+ {
+ return_value = CONN_OPERATION_FAILED;
+ }
+ }
+ if (NULL != res)
+ ldap_msgfree(res);
+ }
+ else {
+ return_value = conn->supports_ds50_repl ? CONN_SUPPORTS_DS5_REPL : CONN_DOES_NOT_SUPPORT_DS5_REPL;
+ }
+ }
+ else
+ {
+ /* Not connected */
+ return_value = CONN_NOT_CONNECTED;
+ }
+ return return_value;
+}
+
+
+
+
+
+/*
+ * Return 1 if "value" is a value of attribute type "type" in entry "entry".
+ * Otherwise, return 0.
+ */
+static int
+attribute_string_value_present(LDAP *ld, LDAPMessage *entry, const char *type,
+ const char *value)
+{
+ int return_value = 0;
+
+ if (NULL != entry)
+ {
+ char *atype = NULL;
+ BerElement *ber = NULL;
+
+ atype = ldap_first_attribute(ld, entry, &ber);
+ while (NULL != atype && 0 == return_value)
+ {
+ if (strcasecmp(atype, type) == 0)
+ {
+ char **strvals = ldap_get_values(ld, entry, atype);
+ int i;
+ for (i = 0; return_value == 0 && NULL != strvals && NULL != strvals[i]; i++)
+ {
+ if (strcmp(strvals[i], value) == 0)
+ {
+ return_value = 1;
+ }
+ }
+ if (NULL != strvals)
+ {
+ ldap_value_free(strvals);
+ }
+ }
+ ldap_memfree(atype);
+ atype = ldap_next_attribute(ld, entry, ber);
+ }
+ if (NULL != ber)
+ ldap_ber_free(ber, 0);
+ /* The last atype has not been freed yet */
+ if (NULL != atype)
+ ldap_memfree(atype);
+ }
+ return return_value;
+}
+
+
+
+
+/*
+ * Read the remote server's schema entry, then read the local schema entry,
+ * and compare the nsschemacsn attribute. If the local csn is newer, or
+ * the remote csn is absent, push the schema down to the consumer.
+ * Return codes:
+ * CONN_SCHEMA_UPDATED if the schema was pushed successfully
+ * CONN_SCHEMA_NO_UPDATE_NEEDED if the schema was as new or newer than
+ * the local server's schema
+ * CONN_OPERATION_FAILED if an error occurred
+ * CONN_NOT_CONNECTED if no connection was active
+ * NOTE: Should only be called when a replication session has been
+ * established by sending a startReplication extended operation.
+ */
+ConnResult
+conn_push_schema(Repl_Connection *conn, CSN **remotecsn)
+{
+ ConnResult return_value = CONN_OPERATION_SUCCESS;
+ char *nsschemacsn = "nsschemacsn";
+ Slapi_Entry **entries = NULL;
+ Slapi_Entry *schema_entry = NULL;
+ int push_schema = 1; /* Assume we need to push for now */
+ int local_error = 0; /* No local error encountered yet */
+ int remote_error = 0; /* No remote error encountered yet */
+ CSN *localcsn = NULL;
+ Slapi_PBlock *spb = NULL;
+ char localcsnstr[CSN_STRSIZE + 1] = {0};
+
+ if (!conn_connected(conn))
+ {
+ return_value = CONN_NOT_CONNECTED;
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: Schema replication update failed: not connected to consumer\n",
+ agmt_get_long_name(conn->agmt));
+ }
+ else
+ {
+ localcsn = dup_global_schema_csn();
+ if (NULL == localcsn)
+ {
+ /* Local server has epoch CSN, so don't push schema */
+ return_value = CONN_SCHEMA_NO_UPDATE_NEEDED;
+ }
+ else if ( remotecsn && *remotecsn && csn_compare(localcsn, *remotecsn) <= 0 )
+ {
+ /* Local server schema is not newer than the remote one */
+ return_value = CONN_SCHEMA_NO_UPDATE_NEEDED;
+ }
+ else
+ {
+ struct berval **remote_schema_csn_bervals = NULL;
+ /* Get remote server's schema */
+ return_value = conn_read_entry_attribute(conn, "cn=schema", nsschemacsn,
+ &remote_schema_csn_bervals);
+ if (CONN_OPERATION_SUCCESS == return_value)
+ {
+ if (NULL != remote_schema_csn_bervals && NULL != remote_schema_csn_bervals[0])
+ {
+ char remotecsnstr[CSN_STRSIZE + 1] = {0};
+ memcpy(remotecsnstr, remote_schema_csn_bervals[0]->bv_val,
+ remote_schema_csn_bervals[0]->bv_len);
+ remotecsnstr[remote_schema_csn_bervals[0]->bv_len] = '\0';
+ *remotecsn = csn_new_by_string(remotecsnstr);
+ if (NULL != remotecsn && (csn_compare(localcsn, *remotecsn) <= 0))
+ {
+ return_value = CONN_SCHEMA_NO_UPDATE_NEEDED;
+ }
+ /* Need to free the remote_schema_csn_bervals */
+ ber_bvecfree(remote_schema_csn_bervals);
+ }
+ }
+ }
+ }
+ if (CONN_OPERATION_SUCCESS == return_value)
+ {
+ /* We know we need to push the schema out. */
+ LDAPMod ocmod = {0};
+ LDAPMod atmod = {0};
+ LDAPMod csnmod = {0};
+ LDAPMod *attrs[4] = {0};
+ int numvalues = 0;
+ Slapi_Attr *attr = NULL;
+ char *csnvalues[2];
+
+ ocmod.mod_type = "objectclasses";
+ ocmod.mod_op = LDAP_MOD_REPLACE | LDAP_MOD_BVALUES;
+ ocmod.mod_bvalues = NULL;
+ atmod.mod_type = "attributetypes";
+ atmod.mod_op = LDAP_MOD_REPLACE | LDAP_MOD_BVALUES;
+ atmod.mod_bvalues = NULL;
+ csnmod.mod_type = nsschemacsn;
+ csnmod.mod_op = LDAP_MOD_REPLACE;
+ csn_as_string (localcsn, PR_FALSE, localcsnstr);
+ csnvalues[0] = localcsnstr;
+ csnvalues[1] = NULL;
+ csnmod.mod_values = csnvalues;
+ attrs[0] = &ocmod;
+ attrs[1] = &atmod;
+ attrs[2] = &csnmod;
+ attrs[3] = NULL;
+
+ return_value = CONN_OPERATION_FAILED; /* assume failure */
+
+ /* Get local schema */
+ spb = slapi_search_internal("cn=schema", LDAP_SCOPE_BASE, "(objectclass=*)",
+ NULL /* controls */, NULL /* schema_csn_attrs */, 0 /* attrsonly */);
+ slapi_pblock_get(spb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries);
+ if (NULL == entries || NULL == entries[0])
+ {
+ /* Whoops - couldn't read our own schema! */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Error: unable to read local schema definitions.\n",
+ agmt_get_long_name(conn->agmt));
+ return_value = CONN_OPERATION_FAILED;
+ }
+ else
+ {
+ schema_entry = entries[0];
+ if (slapi_entry_attr_find(schema_entry, "objectclasses", &attr) != -1)
+ {
+ int i, ind;
+ Slapi_Value *value;
+ slapi_attr_get_numvalues(attr, &numvalues);
+ ocmod.mod_bvalues = (struct berval **)slapi_ch_malloc((numvalues + 1) *
+ sizeof(struct berval *));
+ for (i = 0, ind = slapi_attr_first_value(attr, &value);
+ ind != -1; ind = slapi_attr_next_value(attr, ind, &value), i++)
+ {
+ /* XXXggood had to cast away const below */
+ ocmod.mod_bvalues[i] = (struct berval *)slapi_value_get_berval(value);
+ }
+ ocmod.mod_bvalues[numvalues] = NULL;
+ if (slapi_entry_attr_find(schema_entry, "attributetypes", &attr) != -1)
+ {
+ ConnResult result;
+ slapi_attr_get_numvalues(attr, &numvalues);
+ atmod.mod_bvalues = (struct berval **)slapi_ch_malloc((numvalues + 1) *
+ sizeof(struct berval *));
+ for (i = 0, ind = slapi_attr_first_value(attr, &value);
+ ind != -1; ind = slapi_attr_next_value(attr, ind, &value), i++)
+ {
+ /* XXXggood had to cast away const below */
+ atmod.mod_bvalues[i] = (struct berval *)slapi_value_get_berval(value);
+ }
+ atmod.mod_bvalues[numvalues] = NULL;
+
+ result = conn_send_modify(conn, "cn=schema", attrs, NULL, NULL);
+ switch (result)
+ {
+ case CONN_OPERATION_FAILED:
+ {
+ int ldaperr = -1, optype = -1;
+ conn_get_error(conn, &optype, &ldaperr);
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Schema replication update failed: %s\n",
+ agmt_get_long_name(conn->agmt),
+ ldaperr == -1 ? "Unknown Error" : ldap_err2string(ldaperr));
+ }
+ case CONN_NOT_CONNECTED:
+ return_value = CONN_NOT_CONNECTED;
+ break;
+ case CONN_OPERATION_SUCCESS:
+ return_value = CONN_SCHEMA_UPDATED;
+ break;
+ }
+ }
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Schema replication update failed: "
+ "unable to prepare schema entry for transmission.\n",
+ agmt_get_long_name(conn->agmt));
+ }
+ }
+ /* slapi_ch_free accepts NULL pointer */
+ slapi_ch_free((void **)&ocmod.mod_bvalues);
+ slapi_ch_free((void **)&atmod.mod_bvalues);
+ }
+ if (NULL != spb)
+ {
+ slapi_free_search_results_internal(spb);
+ slapi_pblock_destroy(spb);
+ spb = NULL;
+ }
+ if (NULL != localcsn)
+ {
+ csn_free(&localcsn);
+ }
+ return return_value;
+}
+
+void
+conn_set_timeout(Repl_Connection *conn, long timeout)
+{
+ PR_ASSERT(NULL != conn);
+ PR_ASSERT(timeout >= 0);
+ PR_Lock(conn->lock);
+ conn->timeout.tv_sec = timeout;
+ PR_Unlock(conn->lock);
+}
+
+void conn_set_agmt_changed(Repl_Connection *conn)
+{
+ PR_ASSERT(NULL != conn);
+ PR_Lock(conn->lock);
+ if (NULL != conn->agmt)
+ conn->flag_agmt_changed = 1;
+ PR_Unlock(conn->lock);
+}
+
+/*
+ * Check the result of an ldap_simple_bind operation to see we it
+ * contains the expiration controls
+ * return: -1 error, not bound
+ * 0, OK bind has succeeded
+ */
+static int
+bind_and_check_pwp(Repl_Connection *conn, char * binddn, char *password)
+{
+
+ LDAPControl **ctrls = NULL;
+ LDAPMessage *res = NULL;
+ char *errmsg = NULL;
+ LDAP *ld = conn->ld;
+ int msgid;
+ int *msgidAdr = &msgid;
+ int rc;
+
+ char * optype; /* ldap_simple_bind or slapd_SSL_client_bind */
+
+ if ( conn->transport_flags == TRANSPORT_FLAG_SSL )
+ {
+ char *auth;
+ optype = "ldap_sasl_bind";
+
+ if ( conn->bindmethod == BINDMETHOD_SSL_CLIENTAUTH )
+ {
+ rc = slapd_sasl_ext_client_bind(conn->ld, &msgidAdr);
+ auth = "SSL client authentication";
+
+ if ( rc == LDAP_SUCCESS )
+ {
+ if (conn->last_ldap_error != rc)
+ {
+ conn->last_ldap_error = rc;
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Replication bind with %s resumed\n",
+ agmt_get_long_name(conn->agmt), auth);
+ }
+ }
+ else
+ {
+ /* Do not report the same error over and over again */
+ if (conn->last_ldap_error != rc)
+ {
+ conn->last_ldap_error = rc;
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Replication bind with %s failed: LDAP error %d (%s)\n",
+ agmt_get_long_name(conn->agmt), auth, rc,
+ ldap_err2string(rc));
+ }
+
+ return (CONN_OPERATION_FAILED);
+ }
+ }
+ else
+ {
+ if( ( msgid = do_simple_bind( conn, ld, binddn, password ) ) == -1 )
+ {
+ return (CONN_OPERATION_FAILED);
+ }
+ }
+ }
+ else
+ {
+ optype = "ldap_simple_bind";
+ if( ( msgid = do_simple_bind( conn, ld, binddn, password ) ) == -1 )
+ {
+ return (CONN_OPERATION_FAILED);
+ }
+ }
+
+ /* Wait for the result */
+ if ( ldap_result( ld, msgid, LDAP_MSG_ALL, NULL, &res ) == -1 )
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Received error from consumer for %s operation\n",
+ agmt_get_long_name(conn->agmt), optype);
+
+ return (CONN_OPERATION_FAILED);
+ }
+ /* Don't check ldap_result against 0 because, no timeout is specified */
+
+ /* Free res as we won't use it any longer */
+ if ( ldap_parse_result( ld, res, &rc, NULL, NULL, NULL, &ctrls, 1 /* Free res */)
+ != LDAP_SUCCESS )
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Received error from consumer for %s operation\n",
+ agmt_get_long_name(conn->agmt), optype);
+
+ return (CONN_OPERATION_FAILED);
+ }
+
+ if ( rc == LDAP_SUCCESS )
+ {
+ if ( ctrls )
+ {
+ int i;
+ for( i = 0; ctrls[ i ] != NULL; ++i )
+ {
+ if ( !(strcmp( ctrls[ i ]->ldctl_oid, LDAP_CONTROL_PWEXPIRED)) )
+ {
+ /* Bind is successfull but password has expired */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Succesfully bound %s to consumer, "
+ "but password has expired on consumer.\n",
+ agmt_get_long_name(conn->agmt), binddn);
+ }
+ else if ( !(strcmp( ctrls[ i ]->ldctl_oid, LDAP_CONTROL_PWEXPIRING)) )
+ {
+ /* The password is expiring in n seconds */
+ if ( (ctrls[ i ]->ldctl_value.bv_val != NULL) &&
+ (ctrls[ i ]->ldctl_value.bv_len > 0) )
+ {
+ int password_expiring = atoi( ctrls[ i ]->ldctl_value.bv_val );
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Succesfully bound %s to consumer, "
+ "but password is expiring on consumer in %d seconds.\n",
+ agmt_get_long_name(conn->agmt), binddn, password_expiring);
+ }
+ }
+ }
+ ldap_controls_free( ctrls );
+ }
+
+ return (CONN_OPERATION_SUCCESS);
+ }
+ else
+ {
+ /* errmsg is a pointer directly into the ld structure - do not free */
+ rc = ldap_get_lderrno( ld, NULL, &errmsg );
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Replication bind to %s on consumer failed: %d (%s)\n",
+ agmt_get_long_name(conn->agmt), binddn, rc, errmsg);
+
+ conn->last_ldap_error = rc; /* specific error */
+ return (CONN_OPERATION_FAILED);
+ }
+}
+
+static int
+do_simple_bind (Repl_Connection *conn, LDAP *ld, char * binddn, char *password)
+{
+ int msgid;
+
+ if( ( msgid = ldap_simple_bind( ld, binddn, password ) ) == -1 )
+ {
+ char *ldaperrtext = NULL;
+ int ldaperr;
+ int prerr = PR_GetError();
+
+ ldaperr = ldap_get_lderrno( ld, NULL, &ldaperrtext );
+ /* Do not report the same error over and over again */
+ if (conn->last_ldap_error != ldaperr)
+ {
+ conn->last_ldap_error = ldaperr;
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Simple bind failed, "
+ SLAPI_COMPONENT_NAME_LDAPSDK " error %d (%s), "
+ SLAPI_COMPONENT_NAME_NSPR " error %d (%s)\n",
+ agmt_get_long_name(conn->agmt),
+ ldaperr, ldaperrtext ? ldaperrtext : ldap_err2string(ldaperr),
+ prerr, slapd_pr_strerror(prerr));
+ }
+ }
+ else if (conn->last_ldap_error != LDAP_SUCCESS)
+ {
+ conn->last_ldap_error = LDAP_SUCCESS;
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Simple bind resumed\n",
+ agmt_get_long_name(conn->agmt));
+ }
+ return msgid;
+}
+
+void
+repl5_set_debug_timeout(const char *val)
+{
+ /* val looks like this: seconds[:debuglevel] */
+ /* seconds is the number of seconds to wait until turning on the debug level */
+ /* this should be less than the ldap connection timeout (default 10 minutes) */
+ /* the optional debug level is the error log debugging level to use (default repl) */
+ if (val) {
+ const char *p = strchr(val, ':');
+ s_debug_timeout = atoi(val);
+ if (p) {
+ s_debug_level = atoi(p+1);
+ } else {
+ s_debug_level = 8192;
+ }
+ }
+}
+
+static time_t
+PRTime2time_t (PRTime tm)
+{
+ PRInt64 rt;
+
+ PR_ASSERT (tm);
+
+ LL_DIV(rt, tm, PR_USEC_PER_SEC);
+
+ return (time_t)rt;
+}
+
+static Slapi_Eq_Context
+repl5_start_debug_timeout(int *setlevel)
+{
+ Slapi_Eq_Context eqctx = 0;
+ if (s_debug_timeout && s_debug_level) {
+ time_t now = time(NULL);
+ eqctx = slapi_eq_once(repl5_debug_timeout_callback, setlevel,
+ s_debug_timeout + now);
+ }
+ return eqctx;
+}
+
+static void
+repl5_stop_debug_timeout(Slapi_Eq_Context eqctx, int *setlevel)
+{
+ char buf[20];
+ char msg[SLAPI_DSE_RETURNTEXT_SIZE];
+
+ if (eqctx && !*setlevel) {
+ int found = slapi_eq_cancel(eqctx);
+ }
+
+ if (s_debug_timeout && s_debug_level && *setlevel) {
+ void config_set_errorlog_level(const char *type, char *buf, char *msg, int apply);
+ sprintf(buf, "%d", 0);
+ config_set_errorlog_level("nsslapd-errorlog-level", buf, msg, 1);
+ }
+}
+
+static void
+repl5_debug_timeout_callback(time_t when, void *arg)
+{
+ int *setlevel = (int *)arg;
+ void config_set_errorlog_level(const char *type, char *buf, char *msg, int apply);
+ char buf[20];
+ char msg[SLAPI_DSE_RETURNTEXT_SIZE];
+
+ *setlevel = 1;
+ sprintf(buf, "%d", s_debug_level);
+ config_set_errorlog_level("nsslapd-errorlog-level", buf, msg, 1);
+
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "repl5_debug_timeout_callback: set debug level to %d at %d\n",
+ s_debug_level, when);
+}
diff --git a/ldap/servers/plugins/replication/repl5_inc_protocol.c b/ldap/servers/plugins/replication/repl5_inc_protocol.c
new file mode 100644
index 00000000..a9905a34
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl5_inc_protocol.c
@@ -0,0 +1,1759 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+/* repl5_inc_protocol.c */
+/*
+
+ The Prot_Incremental object implements the DS 5.0 multi-master incremental
+ replication protocol.
+
+
+Stuff to do:
+
+- Need to figure out how asynchronous events end up in here. They are:
+ - entry updated in replicated area.
+ - backoff timeout
+ - enter/leave.
+
+Perhaps these events should be properties of the main protocol.
+*/
+
+#include "repl.h"
+#include "repl5.h"
+#include "repl5_ruv.h"
+#include "repl5_prot_private.h"
+#include "cl5_api.h"
+
+extern int slapi_log_urp;
+
+/*** from proto-slap.h ***/
+void ava_done(struct ava *ava);
+
+typedef struct repl5_inc_private
+{
+ char *ruv; /* RUV on remote replica (use diff type for this? - ggood */
+ Backoff_Timer *backoff;
+ Repl_Protocol *rp;
+ PRLock *lock;
+ PRUint32 eventbits;
+} repl5_inc_private;
+
+
+/* Various states the incremental protocol can pass through */
+#define STATE_START 0 /* ONREPL - should we rename this - we don't use it just to start up? */
+#define STATE_WAIT_WINDOW_OPEN 1
+#define STATE_WAIT_CHANGES 2
+#define STATE_READY_TO_ACQUIRE 3
+#define STATE_BACKOFF_START 4 /* ONREPL - can we combine BACKOFF_START and BACKOFF states? */
+#define STATE_BACKOFF 5
+#define STATE_SENDING_UPDATES 6
+#define STATE_STOP_FATAL_ERROR 7
+#define STATE_STOP_FATAL_ERROR_PART2 8
+#define STATE_STOP_NORMAL_TERMINATION 9
+
+/* Events (synchronous and asynchronous; these are bits) */
+#define EVENT_WINDOW_OPENED 1
+#define EVENT_WINDOW_CLOSED 2
+#define EVENT_TRIGGERING_CRITERIA_MET 4 /* ONREPL - should we rename this to EVENT_CHANGE_AVAILABLE */
+#define EVENT_BACKOFF_EXPIRED 8
+#define EVENT_REPLICATE_NOW 16
+#define EVENT_PROTOCOL_SHUTDOWN 32
+#define EVENT_AGMT_CHANGED 64
+
+#define UPDATE_NO_MORE_UPDATES 201
+#define UPDATE_TRANSIENT_ERROR 202
+#define UPDATE_FATAL_ERROR 203
+#define UPDATE_SCHEDULE_WINDOW_CLOSED 204
+#define UPDATE_CONNECTION_LOST 205
+#define UPDATE_TIMEOUT 206
+#define UPDATE_YIELD 207
+
+/* Return codes from examine_update_vector */
+#define EXAMINE_RUV_PRISTINE_REPLICA 401
+#define EXAMINE_RUV_GENERATION_MISMATCH 402
+#define EXAMINE_RUV_REPLICA_TOO_OLD 403
+#define EXAMINE_RUV_OK 404
+#define EXAMINE_RUV_PARAM_ERROR 405
+
+#define MAX_CHANGES_PER_SESSION 10000
+/*
+ * Maximum time to wait between replication sessions. If we
+ * don't see any updates for a period equal to this interval,
+ * we go ahead and start a replication session, just to be safe
+ */
+#define MAX_WAIT_BETWEEN_SESSIONS PR_SecondsToInterval(60 * 5) /* 5 minutes */
+
+/*
+ * tests if the protocol has been shutdown and we need to quit
+ * event_occurred resets the bits in the bit flag, so whoever tests for shutdown
+ * resets the flags, so the next one who tests for shutdown won't get it, so we
+ * also look at the terminate flag
+ */
+#define PROTOCOL_IS_SHUTDOWN(prp) (event_occurred(prp, EVENT_PROTOCOL_SHUTDOWN) || prp->terminate)
+
+/* Forward declarations */
+static PRUint32 event_occurred(Private_Repl_Protocol *prp, PRUint32 event);
+static void reset_events (Private_Repl_Protocol *prp);
+static void protocol_sleep(Private_Repl_Protocol *prp, PRIntervalTime duration);
+static int send_updates(Private_Repl_Protocol *prp, RUV *ruv, PRUint32 *num_changes_sent);
+static void repl5_inc_backoff_expired(time_t timer_fire_time, void *arg);
+static int examine_update_vector(Private_Repl_Protocol *prp, RUV *ruv);
+static PRBool ignore_error_and_keep_going(int error);
+static const char* state2name (int state);
+static const char* event2name (int event);
+static const char* op2string (int op);
+
+/*
+ * It's specifically ok to delete a protocol instance that
+ * is currently running. The instance will be shut down, and
+ * then resources will be freed. Since a graceful shutdown is
+ * attempted, this function may take some time to complete.
+ */
+static void
+repl5_inc_delete(Private_Repl_Protocol **prpp)
+{
+ /* First, stop the protocol if it isn't already stopped */
+ /* Then, delete all resources used by the protocol */
+}
+
+/* helper function */
+void
+set_pause_and_busy_time(long *pausetime, long *busywaittime)
+{
+ /* If neither are set, set busy time to its default */
+ if (!*pausetime && !*busywaittime)
+ {
+ *busywaittime = PROTOCOL_BUSY_BACKOFF_MINIMUM;
+ }
+ /* pause time must be at least 1 more than the busy backoff time */
+ if (*pausetime && !*busywaittime)
+ {
+ /*
+ * user specified a pause time but no busy wait time - must
+ * set busy wait time to 1 less than pause time - if pause
+ * time is 1, we must set it to 2
+ */
+ if (*pausetime < 2)
+ {
+ *pausetime = 2;
+ }
+ *busywaittime = *pausetime - 1;
+ }
+ else if (!*pausetime && *busywaittime)
+ {
+ /*
+ * user specified a busy wait time but no pause time - must
+ * set pause time to 1 more than busy wait time
+ */
+ *pausetime = *busywaittime + 1;
+ }
+ else if (*pausetime && *busywaittime && *pausetime <= *busywaittime)
+ {
+ /*
+ * user specified both pause and busy wait times, but the pause
+ * time was <= busy wait time - pause time must be at least
+ * 1 more than the busy wait time
+ */
+ *pausetime = *busywaittime + 1;
+ }
+}
+
+/*
+ * Do the incremental protocol.
+ *
+ * What's going on here? This thing is a state machine. It has the
+ * following states:
+ *
+ * State transition table:
+ *
+ * Curr State Condition/Event Next State
+ * ---------- ------------ -----------
+ * START schedule window is open ACQUIRE_REPLICA
+ * schedule window is closed WAIT_WINDOW_OPEN
+ * WAIT_WINDOW_OPEN schedule change START
+ * replicate now ACQUIRE_REPLICA
+ * schedule window opens ACQUIRE_REPLICA
+ * ACQUIRE_REPLICA acquired replica SEND_CHANGES
+ * failed to acquire - transient error START_BACKOFF
+ * failed to acquire - fatal error STOP_FATAL_ERROR
+ * SEND_CHANGES can't update CONSUMER_NEEDS_REINIT
+ * no changes to send WAIT_CHANGES
+ * can't send - thransient error START_BACKOF
+ * can't send - window closed WAIT_WINDOW_OPEN
+ * can'r send - fatal error STOP_FATAL_ERROR
+ * START_BACKOF replicate now ACQUIRE_REPLICA
+ * schedule changes START
+ * schedule window closes WAIT_WINDOW_OPEN
+ * backoff expires & can acquire SEND_CHANGES
+ * backoff expires & can't acquire-trans BACKOFF
+ * backoff expires & can't acquire-fatal STOP_FATAL_ERROR
+ * BACKOF replicate now ACQUIRE_REPLICA
+ * schedule changes START
+ * schedule window closes WAIT_WINDOW_OPEN
+ * backoff expires & can acquire SEND_CHANGES
+ * backoff expires & can't acquire-trans BACKOFF
+ * backoff expires & can't acquire-fatal STOP_FATAL_ERROR
+ * WAIT_CHANGES schedule window closes WAIT_WINDOW_OPEN
+ * replicate_now ACQUIRE_REPLICA
+ * change available ACQUIRE_REPLICA
+ * schedule_change START
+ */
+
+/*
+ * Main state machine for the incremental protocol. This routine will,
+ * under normal circumstances, not return until the protocol is shut
+ * down.
+ */
+static void
+repl5_inc_run(Private_Repl_Protocol *prp)
+{
+ int current_state = STATE_START;
+ int next_state = STATE_START;
+ repl5_inc_private *prp_priv = (repl5_inc_private *)prp->private;
+ int done;
+ int e1;
+ RUV *ruv = NULL;
+ CSN *cons_schema_csn;
+ Replica *replica;
+ int wait_change_timer_set = 0;
+ time_t last_start_time;
+ PRUint32 num_changes_sent;
+ char *hostname = NULL;
+ int portnum = 0;
+ /* use a different backoff timer strategy for ACQUIRE_REPLICA_BUSY errors */
+ PRBool use_busy_backoff_timer = PR_FALSE;
+ long pausetime = 0;
+ long busywaittime = 0;
+
+ prp->stopped = 0;
+ prp->terminate = 0;
+ hostname = agmt_get_hostname(prp->agmt);
+ portnum = agmt_get_port(prp->agmt);
+
+ /* establish_protocol_callbacks(prp); */
+ done = 0;
+ do {
+ int rc;
+
+ /* Take action, based on current state, and compute new state. */
+ switch (current_state)
+ {
+ case STATE_START:
+
+ dev_debug("repl5_inc_run(STATE_START)");
+ if (PROTOCOL_IS_SHUTDOWN(prp))
+ {
+ done = 1;
+ break;
+ }
+
+ /*
+ * Our initial state. See if we're in a schedule window. If
+ * so, then we're ready to acquire the replica and see if it
+ * needs any updates from us. If not, then wait for the window
+ * to open.
+ */
+ if (agmt_schedule_in_window_now(prp->agmt))
+ {
+ next_state = STATE_READY_TO_ACQUIRE;
+ }
+ else
+ {
+ next_state = STATE_WAIT_WINDOW_OPEN;
+ }
+
+ /* we can get here from other states because some events happened and were
+ not cleared. For instance when we wake up in STATE_WAIT_CHANGES state.
+ Since this is a fresh start state, we should clear all events */
+ /* ONREPL - this does not feel right - we should take another look
+ at this state machine */
+ reset_events (prp);
+
+ /* Cancel any linger timer that might be in effect... */
+ conn_cancel_linger(prp->conn);
+ /* ... and disconnect, if currently connected */
+ conn_disconnect(prp->conn);
+ /* get the new pause time, if any */
+ pausetime = agmt_get_pausetime(prp->agmt);
+ /* get the new busy wait time, if any */
+ busywaittime = agmt_get_busywaittime(prp->agmt);
+ if (pausetime || busywaittime)
+ {
+ /* helper function to make sure they are set correctly */
+ set_pause_and_busy_time(&pausetime, &busywaittime);
+ }
+ break;
+ case STATE_WAIT_WINDOW_OPEN:
+ /*
+ * We're waiting for a schedule window to open. If one did,
+ * or we receive a "replicate now" event, then start a protocol
+ * session immediately. If the replication schedule changed, go
+ * back to start. Otherwise, go back to sleep.
+ */
+ dev_debug("repl5_inc_run(STATE_WAIT_WINDOW_OPEN)");
+ if (PROTOCOL_IS_SHUTDOWN(prp))
+ {
+ done = 1;
+ break;
+ }
+ else if (event_occurred(prp, EVENT_WINDOW_OPENED))
+ {
+ next_state = STATE_READY_TO_ACQUIRE;
+ }
+ else if (event_occurred(prp, EVENT_REPLICATE_NOW))
+ {
+ next_state = STATE_READY_TO_ACQUIRE;
+ }
+ else if (event_occurred(prp, EVENT_AGMT_CHANGED))
+ {
+ next_state = STATE_START;
+ conn_set_agmt_changed(prp->conn);
+ }
+ else if (event_occurred(prp, EVENT_TRIGGERING_CRITERIA_MET)) /* change available */
+ {
+ /* just ignore it and go to sleep */
+ protocol_sleep(prp, PR_INTERVAL_NO_TIMEOUT);
+ }
+ else if (e1 = event_occurred(prp, EVENT_WINDOW_CLOSED) ||
+ event_occurred(prp, EVENT_BACKOFF_EXPIRED))
+ {
+ /* this events - should not occur - log a warning and go to sleep */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Incremental protocol: "
+ "event %s should not occur in state %s; going to sleep\n",
+ agmt_get_long_name(prp->agmt),
+ e1 ? event2name(EVENT_WINDOW_CLOSED) : event2name(EVENT_BACKOFF_EXPIRED),
+ state2name(current_state));
+ protocol_sleep(prp, PR_INTERVAL_NO_TIMEOUT);
+ }
+ else
+ {
+ /* wait until window opens or an event occurs */
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: Incremental protocol: "
+ "waiting for update window to open\n", agmt_get_long_name(prp->agmt));
+ protocol_sleep(prp, PR_INTERVAL_NO_TIMEOUT);
+ }
+ break;
+ case STATE_WAIT_CHANGES:
+ /*
+ * We're in a replication window, but we're waiting for more
+ * changes to accumulate before we actually hook up and send
+ * them.
+ */
+ dev_debug("repl5_inc_run(STATE_WAIT_CHANGES)");
+ if (PROTOCOL_IS_SHUTDOWN(prp))
+ {
+ dev_debug("repl5_inc_run(STATE_WAIT_CHANGES): PROTOCOL_IS_SHUTING_DOWN -> end repl5_inc_run\n");
+ done = 1;
+ break;
+ }
+ else if (event_occurred(prp, EVENT_REPLICATE_NOW))
+ {
+ dev_debug("repl5_inc_run(STATE_WAIT_CHANGES): EVENT_REPLICATE_NOW received -> STATE_READY_TO_ACQUIRE\n");
+ next_state = STATE_READY_TO_ACQUIRE;
+ wait_change_timer_set = 0;
+ }
+ else if (event_occurred(prp, EVENT_AGMT_CHANGED))
+ {
+ dev_debug("repl5_inc_run(STATE_WAIT_CHANGES): EVENT_AGMT_CHANGED received -> STATE_START\n");
+ next_state = STATE_START;
+ conn_set_agmt_changed(prp->conn);
+ wait_change_timer_set = 0;
+ }
+ else if (event_occurred(prp, EVENT_WINDOW_CLOSED))
+ {
+ dev_debug("repl5_inc_run(STATE_WAIT_CHANGES): EVENT_WINDOW_CLOSED received -> STATE_WAIT_WINDOW_OPEN\n");
+ next_state = STATE_WAIT_WINDOW_OPEN;
+ wait_change_timer_set = 0;
+ }
+ else if (event_occurred(prp, EVENT_TRIGGERING_CRITERIA_MET))
+ {
+ dev_debug("repl5_inc_run(STATE_WAIT_CHANGES): EVENT_TRIGGERING_CRITERIA_MET received -> STATE_READY_TO_ACQUIRE\n");
+ next_state = STATE_READY_TO_ACQUIRE;
+ wait_change_timer_set = 0;
+ }
+ else if (e1 = event_occurred(prp, EVENT_WINDOW_OPENED) ||
+ event_occurred(prp, EVENT_BACKOFF_EXPIRED))
+ {
+ /* this events - should not occur - log a warning and clear the event */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "%s: Incremental protocol: "
+ "event %s should not occur in state %s\n",
+ agmt_get_long_name(prp->agmt),
+ e1 ? event2name(EVENT_WINDOW_OPENED) : event2name(EVENT_BACKOFF_EXPIRED),
+ state2name(current_state));
+ wait_change_timer_set = 0;
+ }
+ else
+ {
+ if (wait_change_timer_set)
+ {
+ /* We are here because our timer expired */
+ dev_debug("repl5_inc_run(STATE_WAIT_CHANGES): wait_change_timer_set expired -> STATE_START\n");
+ next_state = STATE_START;
+ wait_change_timer_set = 0;
+ }
+ else
+ {
+ /* We are here because the last replication session
+ * finished or aborted.
+ */
+ wait_change_timer_set = 1;
+ protocol_sleep(prp, MAX_WAIT_BETWEEN_SESSIONS);
+ }
+ }
+ break;
+ case STATE_READY_TO_ACQUIRE:
+
+ dev_debug("repl5_inc_run(STATE_READY_TO_ACQUIRE)");
+ if (PROTOCOL_IS_SHUTDOWN(prp))
+ {
+ done = 1;
+ break;
+ }
+
+ /* ONREPL - at this state we unconditionally acquire the replica
+ ignoring all events. Not sure if this is good */
+ object_acquire(prp->replica_object);
+ replica = object_get_data(prp->replica_object);
+
+ rc = acquire_replica(prp, REPL_NSDS50_INCREMENTAL_PROTOCOL_OID, &ruv);
+ use_busy_backoff_timer = PR_FALSE; /* default */
+ if (rc == ACQUIRE_SUCCESS)
+ {
+ next_state = STATE_SENDING_UPDATES;
+ }
+ else if (rc == ACQUIRE_REPLICA_BUSY)
+ {
+ next_state = STATE_BACKOFF_START;
+ use_busy_backoff_timer = PR_TRUE;
+ }
+ else if (rc == ACQUIRE_CONSUMER_WAS_UPTODATE)
+ {
+ next_state = STATE_WAIT_CHANGES;
+ }
+ else if (rc == ACQUIRE_TRANSIENT_ERROR)
+ {
+ next_state = STATE_BACKOFF_START;
+ }
+ else if (rc == ACQUIRE_FATAL_ERROR)
+ {
+ next_state = STATE_STOP_FATAL_ERROR;
+ }
+ if (rc != ACQUIRE_SUCCESS)
+ {
+ int optype, ldaprc;
+ conn_get_error(prp->conn, &optype, &ldaprc);
+ agmt_set_last_update_status(prp->agmt, ldaprc,
+ prp->last_acquire_response_code, NULL);
+ }
+
+ object_release(prp->replica_object); replica = NULL;
+ break;
+ case STATE_BACKOFF_START:
+ dev_debug("repl5_inc_run(STATE_BACKOFF_START)");
+ if (PROTOCOL_IS_SHUTDOWN(prp))
+ {
+ done = 1;
+ break;
+ }
+ if (event_occurred(prp, EVENT_REPLICATE_NOW))
+ {
+ next_state = STATE_READY_TO_ACQUIRE;
+ }
+ else if (event_occurred(prp, EVENT_AGMT_CHANGED))
+ {
+ next_state = STATE_START;
+ conn_set_agmt_changed(prp->conn);
+ }
+ else if (event_occurred (prp, EVENT_WINDOW_CLOSED))
+ {
+ next_state = STATE_WAIT_WINDOW_OPEN;
+ }
+ else if (event_occurred (prp, EVENT_TRIGGERING_CRITERIA_MET))
+ {
+ /* consume and ignore */
+ }
+ else if (e1 = event_occurred (prp, EVENT_WINDOW_OPENED) ||
+ event_occurred (prp, EVENT_BACKOFF_EXPIRED))
+ {
+ /* This should never happen */
+ /* this events - should not occur - log a warning and go to sleep */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Incremental protocol: event %s should not occur in state %s\n",
+ agmt_get_long_name(prp->agmt),
+ e1 ? event2name(EVENT_WINDOW_OPENED) : event2name(EVENT_BACKOFF_EXPIRED),
+ state2name(current_state));
+ }
+ else
+ {
+ /* Set up the backoff timer to wake us up at the appropriate time */
+ if (use_busy_backoff_timer)
+ {
+ /* we received a busy signal from the consumer, wait for a while */
+ if (!busywaittime)
+ {
+ busywaittime = PROTOCOL_BUSY_BACKOFF_MINIMUM;
+ }
+ prp_priv->backoff = backoff_new(BACKOFF_FIXED, busywaittime,
+ busywaittime);
+ }
+ else
+ {
+ prp_priv->backoff = backoff_new(BACKOFF_EXPONENTIAL, PROTOCOL_BACKOFF_MINIMUM,
+ PROTOCOL_BACKOFF_MAXIMUM);
+ }
+ next_state = STATE_BACKOFF;
+ backoff_reset(prp_priv->backoff, repl5_inc_backoff_expired, (void *)prp);
+ protocol_sleep(prp, PR_INTERVAL_NO_TIMEOUT);
+ use_busy_backoff_timer = PR_FALSE;
+ }
+ break;
+ case STATE_BACKOFF:
+ /*
+ * We're in a backoff state.
+ */
+ dev_debug("repl5_inc_run(STATE_BACKOFF)");
+ if (PROTOCOL_IS_SHUTDOWN(prp))
+ {
+ if (prp_priv->backoff)
+ backoff_delete(&prp_priv->backoff);
+ done = 1;
+ break;
+ }
+ else if (event_occurred(prp, EVENT_REPLICATE_NOW))
+ {
+ next_state = STATE_READY_TO_ACQUIRE;
+ }
+ else if (event_occurred(prp, EVENT_AGMT_CHANGED))
+ {
+ next_state = STATE_START;
+
+ conn_set_agmt_changed(prp->conn);
+ /* Destroy the backoff timer, since we won't need it anymore */
+ if (prp_priv->backoff)
+ backoff_delete(&prp_priv->backoff);
+ }
+ else if (event_occurred(prp, EVENT_WINDOW_CLOSED))
+ {
+ next_state = STATE_WAIT_WINDOW_OPEN;
+ /* Destroy the backoff timer, since we won't need it anymore */
+ if (prp_priv->backoff)
+ backoff_delete(&prp_priv->backoff);
+ }
+ else if (event_occurred(prp, EVENT_BACKOFF_EXPIRED))
+ {
+ rc = acquire_replica(prp, REPL_NSDS50_INCREMENTAL_PROTOCOL_OID, &ruv);
+ use_busy_backoff_timer = PR_FALSE;
+ if (rc == ACQUIRE_SUCCESS)
+ {
+ next_state = STATE_SENDING_UPDATES;
+ }
+ else if (rc == ACQUIRE_REPLICA_BUSY)
+ {
+ next_state = STATE_BACKOFF;
+ use_busy_backoff_timer = PR_TRUE;
+ }
+ else if (rc == ACQUIRE_CONSUMER_WAS_UPTODATE)
+ {
+ next_state = STATE_WAIT_CHANGES;
+ }
+ else if (rc == ACQUIRE_TRANSIENT_ERROR)
+ {
+ next_state = STATE_BACKOFF;
+ }
+ else if (rc == ACQUIRE_FATAL_ERROR)
+ {
+ next_state = STATE_STOP_FATAL_ERROR;
+ }
+ if (rc != ACQUIRE_SUCCESS)
+ {
+ int optype, ldaprc;
+ conn_get_error(prp->conn, &optype, &ldaprc);
+ agmt_set_last_update_status(prp->agmt, ldaprc,
+ prp->last_acquire_response_code, NULL);
+ }
+ /*
+ * We either need to step the backoff timer, or
+ * destroy it if we don't need it anymore.
+ */
+ if (STATE_BACKOFF == next_state)
+ {
+ time_t next_fire_time;
+ time_t now;
+ /* Step the backoff timer */
+ time(&now);
+ next_fire_time = backoff_step(prp_priv->backoff);
+ /* And go back to sleep */
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: Replication session backing off for %d seconds\n",
+ agmt_get_long_name(prp->agmt),
+ next_fire_time - now);
+
+ protocol_sleep(prp, PR_INTERVAL_NO_TIMEOUT);
+ }
+ else
+ {
+ /* Destroy the backoff timer, since we won't need it anymore */
+ backoff_delete(&prp_priv->backoff);
+ }
+ use_busy_backoff_timer = PR_FALSE;
+ }
+ else if (event_occurred(prp, EVENT_TRIGGERING_CRITERIA_MET))
+ {
+ /* changes are available */
+ if ( prp_priv->backoff == NULL || backoff_expired (prp_priv->backoff, 60) )
+ {
+ /*
+ * Have seen cases that the agmt stuck here forever since
+ * somehow the backoff timer was not in event queue anymore.
+ * If the backoff timer has expired more than 60 seconds,
+ * destroy it.
+ */
+ if ( prp_priv->backoff )
+ backoff_delete(&prp_priv->backoff);
+ next_state = STATE_READY_TO_ACQUIRE;
+ }
+ else
+ {
+ /* ignore changes and go to sleep */
+ protocol_sleep(prp, PR_INTERVAL_NO_TIMEOUT);
+ }
+ }
+ else if (event_occurred(prp, EVENT_WINDOW_OPENED))
+ {
+ /* this should never happen - log an error and go to sleep */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "%s: Incremental protocol: "
+ "event %s should not occur in state %s; going to sleep\n",
+ agmt_get_long_name(prp->agmt),
+ event2name(EVENT_WINDOW_OPENED), state2name(current_state));
+ protocol_sleep(prp, PR_INTERVAL_NO_TIMEOUT);
+ }
+ break;
+ case STATE_SENDING_UPDATES:
+ dev_debug("repl5_inc_run(STATE_SENDING_UPDATES)");
+ agmt_set_update_in_progress(prp->agmt, PR_TRUE);
+ num_changes_sent = 0;
+ last_start_time = current_time();
+ agmt_set_last_update_start(prp->agmt, last_start_time);
+ /*
+ * We've acquired the replica, and are ready to send any
+ * needed updates.
+ */
+ if (PROTOCOL_IS_SHUTDOWN(prp))
+ {
+ release_replica (prp);
+ done = 1;
+ agmt_set_update_in_progress(prp->agmt, PR_FALSE);
+ agmt_set_last_update_end(prp->agmt, current_time());
+ /* MAB: I don't find the following status correct. How do we know it has
+ been stopped by an admin and not by a total update request, for instance?
+ In any case, how is this protocol shutdown situation different from all the
+ other ones that are present in this state machine? */
+ /* richm: We at least need to let monitors know that the protocol has been
+ shutdown - maybe they can figure out why */
+ agmt_set_last_update_status(prp->agmt, 0, 0, "Protocol stopped");
+ break;
+ }
+
+ agmt_set_last_update_status(prp->agmt, 0, 0, "Incremental update started");
+
+ /* ONREPL - in this state we send changes no matter what other events occur.
+ This is because we can get because of the REPLICATE_NOW event which
+ has high priority. Is this ok? */
+ /* First, push new schema to the consumer if needed */
+ /* ONREPL - should we push schema after we examine the RUV? */
+ /*
+ * GGOOREPL - I don't see why we should wait until we've
+ * examined the RUV. The schema entry has its own CSN that is
+ * used to decide if the remote schema needs to be updated.
+ */
+ cons_schema_csn = agmt_get_consumer_schema_csn ( prp->agmt );
+ rc = conn_push_schema(prp->conn, &cons_schema_csn);
+ if ( cons_schema_csn != agmt_get_consumer_schema_csn ( prp->agmt ))
+ {
+ agmt_set_consumer_schema_csn ( prp->agmt, cons_schema_csn );
+ }
+ if (CONN_SCHEMA_UPDATED != rc && CONN_SCHEMA_NO_UPDATE_NEEDED != rc)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Warning: unable to replicate schema: rc=%d\n",
+ agmt_get_long_name(prp->agmt), rc);
+ /* But keep going */
+ }
+ dev_debug("repl5_inc_run(STATE_SENDING_UPDATES) -> examine_update_vector");
+ rc = examine_update_vector(prp, ruv);
+ /*
+ * Decide what to do next - proceed with incremental,
+ * backoff, or total update
+ */
+ switch (rc)
+ {
+ case EXAMINE_RUV_PARAM_ERROR:
+ /* this is really bad - we have NULL prp! */
+ next_state = STATE_STOP_FATAL_ERROR;
+ break;
+ case EXAMINE_RUV_PRISTINE_REPLICA:
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Replica has no update vector. It has never been initialized.\n",
+ agmt_get_long_name(prp->agmt));
+ next_state = STATE_BACKOFF_START;
+ break;
+ case EXAMINE_RUV_GENERATION_MISMATCH:
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Replica has a different generation ID than the local data.\n",
+ agmt_get_long_name(prp->agmt));
+ next_state = STATE_BACKOFF_START;
+ break;
+ case EXAMINE_RUV_REPLICA_TOO_OLD:
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Replica update vector is too out of date to bring "
+ "into sync using the incremental protocol. The replica "
+ "must be reinitialized.\n", agmt_get_long_name(prp->agmt));
+ next_state = STATE_BACKOFF_START;
+ break;
+ case EXAMINE_RUV_OK:
+ /* update our csn generator state with the consumer's ruv data */
+ dev_debug("repl5_inc_run(STATE_SENDING_UPDATES) -> examine_update_vector OK");
+ object_acquire(prp->replica_object);
+ replica = object_get_data(prp->replica_object);
+ rc = replica_update_csngen_state (replica, ruv);
+ object_release (prp->replica_object);
+ replica = NULL;
+ if (rc != 0) /* too much skew */
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Incremental protocol: fatal error - too much time skew between replicas!\n",
+ agmt_get_long_name(prp->agmt));
+ next_state = STATE_STOP_FATAL_ERROR;
+ }
+ else
+ {
+ rc = send_updates(prp, ruv, &num_changes_sent);
+ if (rc == UPDATE_NO_MORE_UPDATES)
+ {
+ dev_debug("repl5_inc_run(STATE_SENDING_UPDATES) -> send_updates = UPDATE_NO_MORE_UPDATES -> STATE_WAIT_CHANGES");
+ agmt_set_last_update_status(prp->agmt, 0, 0, "Incremental update succeeded");
+ next_state = STATE_WAIT_CHANGES;
+ }
+ else if (rc == UPDATE_YIELD)
+ {
+ dev_debug("repl5_inc_run(STATE_SENDING_UPDATES) -> send_updates = UPDATE_YIELD -> STATE_BACKOFF_START");
+ agmt_set_last_update_status(prp->agmt, 0, 0, "Incremental update succeeded and yielded");
+ next_state = STATE_BACKOFF_START;
+ }
+ else if (rc == UPDATE_TRANSIENT_ERROR)
+ {
+ dev_debug("repl5_inc_run(STATE_SENDING_UPDATES) -> send_updates = UPDATE_TRANSIENT_ERROR -> STATE_BACKOFF_START");
+ next_state = STATE_BACKOFF_START;
+ }
+ else if (rc == UPDATE_FATAL_ERROR)
+ {
+ dev_debug("repl5_inc_run(STATE_SENDING_UPDATES) -> send_updates = UPDATE_FATAL_ERROR -> STATE_STOP_FATAL_ERROR");
+ next_state = STATE_STOP_FATAL_ERROR;
+ }
+ else if (rc == UPDATE_SCHEDULE_WINDOW_CLOSED)
+ {
+ dev_debug("repl5_inc_run(STATE_SENDING_UPDATES) -> send_updates = UPDATE_SCHEDULE_WINDOW_CLOSED -> STATE_WAIT_WINDOW_OPEN");
+ /* ONREPL - I don't think we should check this. We might be
+ here because of replicate_now event - so we don't care
+ about the schedule */
+ next_state = STATE_WAIT_WINDOW_OPEN;
+ /* ONREPL - do we need to release the replica here ? */
+ conn_disconnect (prp->conn);
+ }
+ else if (rc == UPDATE_CONNECTION_LOST)
+ {
+ dev_debug("repl5_inc_run(STATE_SENDING_UPDATES) -> send_updates = UPDATE_CONNECTION_LOST -> STATE_BACKOFF_START");
+ next_state = STATE_BACKOFF_START;
+ }
+ else if (rc == UPDATE_TIMEOUT)
+ {
+ dev_debug("repl5_inc_run(STATE_SENDING_UPDATES) -> send_updates = UPDATE_TIMEOUT -> STATE_BACKOFF_START");
+ next_state = STATE_BACKOFF_START;
+ }
+ }
+ last_start_time = 0UL;
+ break;
+ }
+ if (NULL != ruv)
+ {
+ ruv_destroy(&ruv); ruv = NULL;
+ }
+ agmt_set_last_update_end(prp->agmt, current_time());
+ agmt_set_update_in_progress(prp->agmt, PR_FALSE);
+ /* If timed out, close the connection after released the replica */
+ release_replica(prp);
+ if (rc == UPDATE_TIMEOUT) {
+ conn_disconnect(prp->conn);
+ }
+ if (rc == UPDATE_NO_MORE_UPDATES && num_changes_sent > 0)
+ {
+ if (pausetime > 0)
+ {
+ /* richm - 20020219 - If we have acquired the consumer, and another master has gone
+ into backoff waiting for us to release it, we may acquire the replica sooner
+ than the other master has a chance to, and the other master may not be able
+ to acquire the consumer for a long time (hours, days?) if this server is
+ under a heavy load (see reliab06 et. al. system tests)
+ So, this sleep gives the other master(s) a chance to acquire the consumer
+ replica */
+ long loops = pausetime;
+ /* the while loop is so that we don't just sleep and sleep if an
+ event comes in that we should handle immediately (like shutdown) */
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: Pausing updates for %ld seconds to allow other suppliers to update consumer\n",
+ agmt_get_long_name(prp->agmt), pausetime);
+ while (loops-- && !(PROTOCOL_IS_SHUTDOWN(prp)))
+ {
+ DS_Sleep(PR_SecondsToInterval(1));
+ }
+ }
+ else if (num_changes_sent > 10)
+ {
+ /* wait for consumer to write its ruv if the replication was busy */
+ /* When asked, consumer sends its ruv in cache to the supplier. */
+ /* DS_Sleep ( PR_SecondsToInterval(1) ); */
+ }
+ }
+ break;
+ case STATE_STOP_FATAL_ERROR:
+ /*
+ * We encountered some sort of a fatal error. Suspend.
+ */
+ /* XXXggood update state in replica */
+ agmt_set_last_update_status(prp->agmt, -1, 0, "Incremental update has failed and requires administrator action");
+ dev_debug("repl5_inc_run(STATE_STOP_FATAL_ERROR)");
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Incremental update failed and requires administrator action\n",
+ agmt_get_long_name(prp->agmt));
+ next_state = STATE_STOP_FATAL_ERROR_PART2;
+ break;
+ case STATE_STOP_FATAL_ERROR_PART2:
+ if (PROTOCOL_IS_SHUTDOWN(prp))
+ {
+ done = 1;
+ break;
+ }
+
+ /* MAB: This state is the FATAL state where we are supposed to get
+ as a result of a FATAL error on send_updates. But, as bug
+ states, send_updates was always returning TRANSIENT errors and never
+ FATAL... In other words, this code has never been tested before...
+
+ As of 01/16/01, this piece of code was in a very dangerous state. In particular,
+ 1) it does not catch any events
+ 2) it is a terminal state (once reached it never transitions to a different state)
+
+ Both things combined make this state to become a consuming infinite loop
+ that is useless after all (we are in a fatal place requiring manual admin jobs */
+
+ /* MAB: The following lines fix problem number 1 above... When the code gets
+ into this state, it should only get a chance to get out of it by an
+ EVENT_AGMT_CHANGED event... All other events should be ignored */
+ else if (event_occurred(prp, EVENT_AGMT_CHANGED))
+ {
+ dev_debug("repl5_inc_run(STATE_STOP_FATAL_ERROR): EVENT_AGMT_CHANGED received\n");
+ /* Chance to recover for the EVENT_AGMT_CHANGED event.
+ This is not mandatory, but fixes problem 2 above */
+ next_state = STATE_STOP_NORMAL_TERMINATION;
+ }
+ else
+ {
+ dev_debug("repl5_inc_run(STATE_STOP_FATAL_ERROR): Event received. Clearing it\n");
+ reset_events (prp);
+ }
+
+ protocol_sleep (prp, PR_INTERVAL_NO_TIMEOUT);
+ break;
+
+ case STATE_STOP_NORMAL_TERMINATION:
+ /*
+ * We encountered some sort of a fatal error. Return.
+ */
+ /* XXXggood update state in replica */
+ dev_debug("repl5_inc_run(STATE_STOP_NORMAL_TERMINATION)");
+ done = 1;
+ break;
+ }
+
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: State: %s -> %s\n",
+ agmt_get_long_name(prp->agmt),
+ state2name(current_state), state2name(next_state));
+
+ current_state = next_state;
+ } while (!done);
+ slapi_ch_free((void**)&hostname);
+ /* remove_protocol_callbacks(prp); */
+ prp->stopped = 1;
+ /* Cancel any linger timer that might be in effect... */
+ conn_cancel_linger(prp->conn);
+ /* ... and disconnect, if currently connected */
+ conn_disconnect(prp->conn);
+}
+
+
+
+/*
+ * Go to sleep until awakened.
+ */
+static void
+protocol_sleep(Private_Repl_Protocol *prp, PRIntervalTime duration)
+{
+ PR_ASSERT(NULL != prp);
+ PR_Lock(prp->lock);
+ /* we should not go to sleep if there are events available to be processed.
+ Otherwise, we can miss the event that suppose to wake us up */
+ if (prp->eventbits == 0)
+ PR_WaitCondVar(prp->cvar, duration);
+ else
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: Incremental protocol: can't go to sleep: event bits - %x\n",
+ agmt_get_long_name(prp->agmt), prp->eventbits);
+ }
+ PR_Unlock(prp->lock);
+}
+
+
+/*
+ * Notify the protocol about some event. Signal the condition
+ * variable in case the protocol is sleeping. Multiple occurences
+ * of a single event type are not remembered (e.g. no stack
+ * of events is maintained).
+ */
+static void
+event_notify(Private_Repl_Protocol *prp, PRUint32 event)
+{
+ PR_ASSERT(NULL != prp);
+ PR_Lock(prp->lock);
+ prp->eventbits |= event;
+ PR_NotifyCondVar(prp->cvar);
+ PR_Unlock(prp->lock);
+}
+
+
+/*
+ * Test to see if an event occurred. The event is cleared when
+ * read.
+ */
+static PRUint32
+event_occurred(Private_Repl_Protocol *prp, PRUint32 event)
+{
+ PRUint32 return_value;
+ PR_ASSERT(NULL != prp);
+ PR_Lock(prp->lock);
+ return_value = (prp->eventbits & event);
+ prp->eventbits &= ~event; /* Clear event */
+ PR_Unlock(prp->lock);
+ return return_value;
+}
+
+static void
+reset_events (Private_Repl_Protocol *prp)
+{
+ PR_ASSERT(NULL != prp);
+ PR_Lock(prp->lock);
+ prp->eventbits = 0;
+ PR_Unlock(prp->lock);
+}
+
+
+/*
+ * Replay the actual update to the consumer. Construct an appropriate LDAP
+ * operation, attach the baggage LDAPv3 control that contains the CSN, etc.,
+ * and send the operation to the consumer.
+ */
+ConnResult
+replay_update(Private_Repl_Protocol *prp, slapi_operation_parameters *op)
+{
+ ConnResult return_value;
+ LDAPControl *update_control;
+ char *parentuniqueid;
+ LDAPMod **modrdn_mods = NULL;
+ char csn_str[CSN_STRSIZE]; /* For logging only */
+
+ csn_as_string(op->csn, PR_FALSE, csn_str);
+
+ /* Construct the replication info control that accompanies the operation */
+ if (SLAPI_OPERATION_ADD == op->operation_type)
+ {
+ parentuniqueid = op->p.p_add.parentuniqueid;
+ }
+ else if (SLAPI_OPERATION_MODRDN == op->operation_type)
+ {
+ /*
+ * For modrdn operations, we need to send along modified attributes, e.g.
+ * modifytimestamp.
+ * And the superior_uniqueid !
+ */
+ modrdn_mods = op->p.p_modrdn.modrdn_mods;
+ parentuniqueid = op->p.p_modrdn.modrdn_newsuperior_address.uniqueid;
+ }
+ else
+ {
+ parentuniqueid = NULL;
+ }
+ if (create_NSDS50ReplUpdateInfoControl(op->target_address.uniqueid,
+ parentuniqueid, op->csn, modrdn_mods, &update_control) != LDAP_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: replay_update: Unable to create NSDS50ReplUpdateInfoControl "
+ "for operation with csn %s. Skipping update.\n",
+ agmt_get_long_name(prp->agmt), csn_str);
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: replay_update: Sending %s operation (dn=\"%s\" csn=%s)\n",
+ agmt_get_long_name(prp->agmt),
+ op2string(op->operation_type), op->target_address.dn, csn_str);
+ /* What type of operation is it? */
+ switch (op->operation_type)
+ {
+ case SLAPI_OPERATION_ADD:
+ {
+ LDAPMod **entryattrs;
+ /* Convert entry to mods */
+ (void)slapi_entry2mods (op->p.p_add.target_entry,
+ NULL /* &entrydn : We don't need it */,
+ &entryattrs);
+ if (NULL == entryattrs)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: replay_update: Cannot convert entry to LDAPMods.\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = CONN_LOCAL_ERROR;
+ }
+ else
+ {
+ return_value = conn_send_add(prp->conn, op->target_address.dn,
+ entryattrs, update_control, NULL /* returned controls */);
+ ldap_mods_free(entryattrs, 1);
+ }
+ break;
+ }
+ case SLAPI_OPERATION_MODIFY:
+ return_value = conn_send_modify(prp->conn, op->target_address.dn,
+ op->p.p_modify.modify_mods, update_control,
+ NULL /* returned controls */);
+ break;
+ case SLAPI_OPERATION_DELETE:
+ return_value = conn_send_delete(prp->conn, op->target_address.dn,
+ update_control, NULL /* returned controls */);
+ break;
+ case SLAPI_OPERATION_MODRDN:
+ /* XXXggood need to pass modrdn mods in update control! */
+ return_value = conn_send_rename(prp->conn, op->target_address.dn,
+ op->p.p_modrdn.modrdn_newrdn,
+ op->p.p_modrdn.modrdn_newsuperior_address.dn,
+ op->p.p_modrdn.modrdn_deloldrdn,
+ update_control, NULL /* returned controls */);
+ break;
+ default:
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "%s: replay_update: Unknown "
+ "operation type %d found in changelog - skipping change.\n",
+ agmt_get_long_name(prp->agmt), op->operation_type);
+ }
+
+ destroy_NSDS50ReplUpdateInfoControl(&update_control);
+ }
+
+ if (CONN_OPERATION_SUCCESS == return_value)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: replay_update: Consumer successfully replayed operation with csn %s\n",
+ agmt_get_long_name(prp->agmt), csn_str);
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: replay_update: Consumer could not replay operation with csn %s\n",
+ agmt_get_long_name(prp->agmt), csn_str);
+ }
+ return return_value;
+}
+
+static PRBool
+is_dummy_operation (const slapi_operation_parameters *op)
+{
+ return (strcmp (op->target_address.uniqueid, START_ITERATION_ENTRY_UNIQUEID) == 0);
+}
+
+
+
+void
+cl5_operation_parameters_done (struct slapi_operation_parameters *sop)
+{
+ if(sop!=NULL) {
+ switch(sop->operation_type)
+ {
+ case SLAPI_OPERATION_BIND:
+ slapi_ch_free((void **)&(sop->p.p_bind.bind_saslmechanism));
+ if (sop->p.p_bind.bind_creds)
+ ber_bvecfree((struct berval**)&(sop->p.p_bind.bind_creds));
+ if (sop->p.p_bind.bind_ret_saslcreds)
+ ber_bvecfree((struct berval**)&(sop->p.p_bind.bind_ret_saslcreds));
+ sop->p.p_bind.bind_creds = NULL;
+ sop->p.p_bind.bind_ret_saslcreds = NULL;
+ break;
+ case SLAPI_OPERATION_COMPARE:
+ ava_done((struct ava *)&(sop->p.p_compare.compare_ava));
+ break;
+ case SLAPI_OPERATION_SEARCH:
+ slapi_ch_free((void **)&(sop->p.p_search.search_strfilter));
+ charray_free(sop->p.p_search.search_attrs);
+ slapi_filter_free(sop->p.p_search.search_filter,1);
+ break;
+ case SLAPI_OPERATION_MODRDN:
+ sop->p.p_modrdn.modrdn_deloldrdn = 0;
+ break;
+ case SLAPI_OPERATION_EXTENDED:
+ slapi_ch_free((void **)&(sop->p.p_extended.exop_oid));
+ if (sop->p.p_extended.exop_value)
+ ber_bvecfree((struct berval**)&(sop->p.p_extended.exop_value));
+ sop->p.p_extended.exop_value = NULL;
+ break;
+ default:
+ break;
+ }
+ }
+ operation_parameters_done(sop);
+
+}
+
+
+
+/*
+ * Send a set of updates to the replica. Assumes that (1) the replica
+ * has already been acquired, (2) that the consumer's update vector has
+ * been checked and (3) that it's ok to send incremental updates.
+ * Returns:
+ * UPDATE_NO_MORE_UPDATES - all updates were sent succussfully
+ * UPDATE_TRANSIENT_ERROR - some non-permanent error occurred. Try again later.
+ * UPDATE_FATAL_ERROR - some bad, permanent error occurred.
+ * UPDATE_SCHEDULE_WINDOW_CLOSED - the schedule window closed on us.
+ */
+static int
+send_updates(Private_Repl_Protocol *prp, RUV *remote_update_vector, PRUint32 *num_changes_sent)
+{
+ CL5Entry entry;
+ slapi_operation_parameters op;
+ int return_value;
+ int rc;
+ CL5ReplayIterator *changelog_iterator;
+
+ *num_changes_sent = 0;
+ /*
+ * Iterate over the changelog. Retrieve each update,
+ * construct an appropriate LDAP operation,
+ * attaching the CSN, and send the change.
+ */
+
+ rc = cl5CreateReplayIterator(prp, remote_update_vector, &changelog_iterator);
+ if (CL5_SUCCESS != rc)
+ {
+ switch (rc)
+ {
+ case CL5_BAD_DATA: /* invalid parameter passed to the function */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Invalid parameter passed to cl5CreateReplayIterator\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = UPDATE_FATAL_ERROR;
+ break;
+ case CL5_BAD_FORMAT: /* db data has unexpected format */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Unexpected format encountered in changelog database\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = UPDATE_FATAL_ERROR;
+ break;
+ case CL5_BAD_STATE: /* changelog is in an incorrect state for attempted operation */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Changelog database was in an incorrect state\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = UPDATE_FATAL_ERROR;
+ break;
+ case CL5_BAD_DBVERSION: /* changelog has invalid dbversion */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Incorrect dbversion found in changelog database\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = UPDATE_FATAL_ERROR;
+ break;
+ case CL5_DB_ERROR: /* database error */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: A changelog database error was encountered\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = UPDATE_FATAL_ERROR;
+ break;
+ case CL5_NOTFOUND: /* we have no changes to send */
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: No changes to send\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = UPDATE_NO_MORE_UPDATES;
+ break;
+ case CL5_MEMORY_ERROR: /* memory allocation failed */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Memory allocation error occurred\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = UPDATE_FATAL_ERROR;
+ break;
+ case CL5_SYSTEM_ERROR: /* NSPR error occurred: use PR_GetError for furhter info */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: An NSPR error (%d) occurred\n",
+ agmt_get_long_name(prp->agmt), PR_GetError());
+ return_value = UPDATE_TRANSIENT_ERROR;
+ break;
+ case CL5_CSN_ERROR: /* CSN API failed */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: A CSN API failure was encountered\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = UPDATE_TRANSIENT_ERROR;
+ break;
+ case CL5_RUV_ERROR: /* RUV API failed */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: An RUV API failure occurred\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = UPDATE_TRANSIENT_ERROR;
+ break;
+ case CL5_OBJSET_ERROR: /* namedobjset api failed */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: A namedobject API failure occurred\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = UPDATE_TRANSIENT_ERROR;
+ break;
+ case CL5_PURGED_DATA: /* requested data has been purged */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Data required to update replica has been purged. "
+ "The replica must be reinitialized.\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = UPDATE_FATAL_ERROR;
+ break;
+ case CL5_MISSING_DATA: /* data should be in the changelog, but is missing */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Missing data encountered\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = UPDATE_FATAL_ERROR;
+ break;
+ case CL5_UNKNOWN_ERROR: /* unclassified error */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: An unknown error was ecountered\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = UPDATE_TRANSIENT_ERROR;
+ break;
+ default:
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: An unknown error (%d) occurred "
+ "(cl5CreateReplayIterator)\n",
+ agmt_get_long_name(prp->agmt), rc);
+ return_value = UPDATE_TRANSIENT_ERROR;
+ }
+ }
+ else
+ {
+ int finished = 0;
+ ConnResult replay_crc;
+ char csn_str[CSN_STRSIZE];
+
+ memset ( (void*)&op, 0, sizeof (op) );
+ entry.op = &op;
+ do {
+ cl5_operation_parameters_done ( entry.op );
+ memset ( (void*)entry.op, 0, sizeof (op) );
+ rc = cl5GetNextOperationToReplay(changelog_iterator, &entry);
+ switch (rc)
+ {
+ case CL5_SUCCESS:
+ /* check that we don't return dummy entries */
+ if (is_dummy_operation (entry.op))
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: changelog iteration code returned a dummy entry with csn %s, "
+ "skipping ...\n",
+ agmt_get_long_name(prp->agmt), csn_as_string(entry.op->csn, PR_FALSE, csn_str));
+ continue;
+ }
+ replay_crc = replay_update(prp, entry.op);
+ if (CONN_OPERATION_SUCCESS != replay_crc)
+ {
+ int operation, error;
+ conn_get_error(prp->conn, &operation, &error);
+ csn_as_string(entry.op->csn, PR_FALSE, csn_str);
+ /* Figure out what to do next */
+ if (CONN_OPERATION_FAILED == replay_crc)
+ {
+ /* Map ldap error code to return value */
+ if (!ignore_error_and_keep_going(error))
+ {
+ return_value = UPDATE_TRANSIENT_ERROR;
+ finished = 1;
+ }
+ else
+ {
+ agmt_inc_last_update_changecount (prp->agmt, csn_get_replicaid(entry.op->csn), 1 /*skipped*/);
+ }
+ slapi_log_error(finished ? SLAPI_LOG_FATAL : slapi_log_urp, repl_plugin_name,
+ "%s: Consumer failed to replay change (uniqueid %s, CSN %s): %s. %s.\n",
+ agmt_get_long_name(prp->agmt),
+ entry.op->target_address.uniqueid, csn_str,
+ ldap_err2string(error),
+ finished ? "Will retry later" : "Skipping");
+ }
+ else if (CONN_NOT_CONNECTED == replay_crc)
+ {
+ /* We lost the connection - enter backoff state */
+
+ return_value = UPDATE_TRANSIENT_ERROR;
+ finished = 1;
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Consumer failed to replay change (uniqueid %s, CSN %s): "
+ "%s. Will retry later.\n",
+ agmt_get_long_name(prp->agmt),
+ entry.op->target_address.uniqueid, csn_str,
+ error ? ldap_err2string(error) : "Connection lost");
+ }
+ else if (CONN_TIMEOUT == replay_crc)
+ {
+ return_value = UPDATE_TIMEOUT;
+ finished = 1;
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Consumer timed out to replay change (uniqueid %s, CSN %s): "
+ "%s.\n",
+ agmt_get_long_name(prp->agmt),
+ entry.op->target_address.uniqueid, csn_str,
+ error ? ldap_err2string(error) : "Timeout");
+ }
+ else if (CONN_LOCAL_ERROR == replay_crc)
+ {
+ /*
+ * Something bad happened on the local server - enter
+ * backoff state.
+ */
+ return_value = UPDATE_TRANSIENT_ERROR;
+ finished = 1;
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Failed to replay change (uniqueid %s, CSN %s): "
+ "Local error. Will retry later.\n",
+ agmt_get_long_name(prp->agmt),
+ entry.op->target_address.uniqueid, csn_str);
+ }
+
+ }
+ else
+ {
+ /* Positive response received */
+ (*num_changes_sent)++;
+ agmt_inc_last_update_changecount (prp->agmt, csn_get_replicaid(entry.op->csn), 0 /*replayed*/);
+ }
+ break;
+ case CL5_BAD_DATA:
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Invalid parameter passed to cl5GetNextOperationToReplay\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = UPDATE_FATAL_ERROR;
+ finished = 1;
+ break;
+ case CL5_NOTFOUND:
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: No more updates to send (cl5GetNextOperationToReplay)\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = UPDATE_NO_MORE_UPDATES;
+ finished = 1;
+ break;
+ case CL5_DB_ERROR:
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: A database error occurred (cl5GetNextOperationToReplay)\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = UPDATE_FATAL_ERROR;
+ finished = 1;
+ break;
+ case CL5_BAD_FORMAT:
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: A malformed changelog entry was encountered (cl5GetNextOperationToReplay)\n",
+ agmt_get_long_name(prp->agmt));
+ break;
+ case CL5_MEMORY_ERROR:
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: A memory allocation error occurred (cl5GetNextOperationToRepla)\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = UPDATE_FATAL_ERROR;
+ break;
+ default:
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Unknown error code (%d) returned from cl5GetNextOperationToReplay\n",
+ agmt_get_long_name(prp->agmt), rc);
+ return_value = UPDATE_TRANSIENT_ERROR;
+ break;
+ }
+ /* Check for protocol shutdown */
+ if (prp->terminate)
+ {
+ return_value = UPDATE_NO_MORE_UPDATES;
+ finished = 1;
+ }
+ if (*num_changes_sent >= MAX_CHANGES_PER_SESSION)
+ {
+ return_value = UPDATE_YIELD;
+ finished = 1;
+ }
+ } while (!finished);
+ cl5_operation_parameters_done ( entry.op );
+ cl5DestroyReplayIterator(&changelog_iterator);
+ }
+ return return_value;
+}
+
+
+
+/*
+ * XXXggood this should probably be in the superclass, since the full update
+ * protocol is going to need it too.
+ */
+static int
+repl5_inc_stop(Private_Repl_Protocol *prp)
+{
+ int return_value;
+ PRIntervalTime start, maxwait, now;
+ int seconds = 1200;
+
+ maxwait = PR_SecondsToInterval(seconds);
+ prp->terminate = 1;
+ event_notify(prp, EVENT_PROTOCOL_SHUTDOWN);
+ start = PR_IntervalNow();
+ now = start;
+ while (!prp->stopped && ((now - start) < maxwait))
+ {
+ DS_Sleep(PR_SecondsToInterval(1));
+ now = PR_IntervalNow();
+ }
+ if (!prp->stopped)
+ {
+ /* Isn't listening. Do something drastic. */
+ return_value = -1;
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: repl5_inc_stop: protocol does not stop after %d seconds\n",
+ agmt_get_long_name(prp->agmt), seconds);
+ }
+ else
+ {
+ return_value = 0;
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: repl5_inc_stop: protocol stopped after %d seconds\n",
+ agmt_get_long_name(prp->agmt),
+ PR_IntervalToSeconds(now-start));
+ }
+ return return_value;
+}
+
+
+
+static int
+repl5_inc_status(Private_Repl_Protocol *prp)
+{
+ int return_value = 0;
+
+ return return_value;
+}
+
+
+
+static void
+repl5_inc_notify_update(Private_Repl_Protocol *prp)
+{
+ event_notify(prp, EVENT_TRIGGERING_CRITERIA_MET);
+}
+
+
+static void
+repl5_inc_update_now(Private_Repl_Protocol *prp)
+{
+ event_notify(prp, EVENT_REPLICATE_NOW);
+}
+
+
+static void
+repl5_inc_notify_agmt_changed(Private_Repl_Protocol *prp)
+{
+ event_notify(prp, EVENT_AGMT_CHANGED);
+}
+
+static void
+repl5_inc_notify_window_opened (Private_Repl_Protocol *prp)
+{
+ event_notify(prp, EVENT_WINDOW_OPENED);
+}
+
+static void
+repl5_inc_notify_window_closed (Private_Repl_Protocol *prp)
+{
+ event_notify(prp, EVENT_WINDOW_CLOSED);
+}
+
+Private_Repl_Protocol *
+Repl_5_Inc_Protocol_new(Repl_Protocol *rp)
+{
+ repl5_inc_private *rip = NULL;
+ Private_Repl_Protocol *prp = (Private_Repl_Protocol *)slapi_ch_malloc(sizeof(Private_Repl_Protocol));
+ prp->delete = repl5_inc_delete;
+ prp->run = repl5_inc_run;
+ prp->stop = repl5_inc_stop;
+ prp->status = repl5_inc_status;
+ prp->notify_update = repl5_inc_notify_update;
+ prp->notify_agmt_changed = repl5_inc_notify_agmt_changed;
+ prp->notify_window_opened = repl5_inc_notify_window_opened;
+ prp->notify_window_closed = repl5_inc_notify_window_closed;
+ prp->update_now = repl5_inc_update_now;
+ prp->replica_object = prot_get_replica_object(rp);
+ if ((prp->lock = PR_NewLock()) == NULL)
+ {
+ goto loser;
+ }
+ if ((prp->cvar = PR_NewCondVar(prp->lock)) == NULL)
+ {
+ goto loser;
+ }
+ prp->stopped = 0;
+ prp->terminate = 0;
+ prp->eventbits = 0;
+ prp->conn = prot_get_connection(rp);
+ prp->agmt = prot_get_agreement(rp);
+ prp->last_acquire_response_code = NSDS50_REPL_REPLICA_READY;
+ rip = (void *)slapi_ch_malloc(sizeof(repl5_inc_private));
+ rip->ruv = NULL;
+ rip->backoff = NULL;
+ rip->rp = rp;
+ prp->private = (void *)rip;
+ prp->replica_acquired = PR_FALSE;
+ return prp;
+loser:
+ repl5_inc_delete(&prp);
+ return NULL;
+}
+
+
+
+
+static void
+repl5_inc_backoff_expired(time_t timer_fire_time, void *arg)
+{
+ Private_Repl_Protocol *prp = (Private_Repl_Protocol *)arg;
+ PR_ASSERT(NULL != prp);
+ event_notify(prp, EVENT_BACKOFF_EXPIRED);
+}
+
+
+
+/*
+ * Examine the update vector and determine our course of action.
+ * There are 3 different possibilities, plus a catch-all error:
+ * 1 - no update vector (ruv is NULL). The consumer's replica is
+ * pristine, so it needs to be initialized. Return
+ * EXAMINE_RUV_PRISTINE_REPLICA.
+ * 2 - ruv is present, but its database generation ID doesn't
+ * match the local generation ID. This means that either
+ * the local replica must be reinitialized from the remote
+ * replica or vice-versa. Return
+ * EXAMINE_RUV_GENERATION_MISMATCH.
+ * 3 - ruv is present, and we have all updates needed to bring
+ * the replica up to date using the incremental protocol.
+ * return EXAMINE_RUV_OK.
+ * 4 - parameter error. Return EXAMINE_RUV_PARAM_ERROR
+ */
+static int
+examine_update_vector(Private_Repl_Protocol *prp, RUV *remote_ruv)
+{
+ int return_value;
+
+ PR_ASSERT(NULL != prp);
+ if (NULL == prp)
+ {
+ return_value = EXAMINE_RUV_PARAM_ERROR;
+ }
+ else if (NULL == remote_ruv)
+ {
+ return_value = EXAMINE_RUV_PRISTINE_REPLICA;
+ }
+ else
+ {
+ char *local_gen = NULL;
+ char *remote_gen = ruv_get_replica_generation(remote_ruv);
+ Object *local_ruv_obj;
+ RUV *local_ruv;
+ Replica *replica;
+
+ PR_ASSERT(NULL != prp->replica_object);
+ replica = object_get_data(prp->replica_object);
+ PR_ASSERT(NULL != replica);
+ local_ruv_obj = replica_get_ruv (replica);
+ if (NULL != local_ruv_obj)
+ {
+ local_ruv = (RUV*)object_get_data (local_ruv_obj);
+ PR_ASSERT (local_ruv);
+ local_gen = ruv_get_replica_generation(local_ruv);
+ object_release (local_ruv_obj);
+ }
+ if (NULL == remote_gen || NULL == local_gen || strcmp(remote_gen, local_gen) != 0)
+ {
+ return_value = EXAMINE_RUV_GENERATION_MISMATCH;
+ }
+ else
+ {
+ return_value = EXAMINE_RUV_OK;
+ }
+ slapi_ch_free((void**)&remote_gen);
+ slapi_ch_free((void**)&local_gen);
+ }
+ return return_value;
+}
+
+
+/*
+ * When we get an error from an LDAP operation, we call this
+ * function to decide if we should just keep replaying
+ * updates, or if we should stop, back off, and try again
+ * later.
+ * Returns PR_TRUE if we shoould keep going, PR_FALSE if
+ * we should back off and try again later.
+ *
+ * In general, we keep going if the return code is consistent
+ * with some sort of bug in URP that causes the consumer to
+ * emit an error code that it shouldn't have, e.g. LDAP_ALREADY_EXISTS.
+ *
+ * We stop if there's some indication that the server just completely
+ * failed to process the operation, e.g. LDAP_OPERATIONS_ERROR.
+ */
+static PRBool
+ignore_error_and_keep_going(int error)
+{
+ int return_value;
+
+ switch (error)
+ {
+ /* Cases where we keep going */
+ case LDAP_SUCCESS:
+ case LDAP_NO_SUCH_ATTRIBUTE:
+ case LDAP_UNDEFINED_TYPE:
+ case LDAP_CONSTRAINT_VIOLATION:
+ case LDAP_TYPE_OR_VALUE_EXISTS:
+ case LDAP_INVALID_SYNTAX:
+ case LDAP_NO_SUCH_OBJECT:
+ case LDAP_INVALID_DN_SYNTAX:
+ case LDAP_IS_LEAF:
+ case LDAP_INSUFFICIENT_ACCESS:
+ case LDAP_NAMING_VIOLATION:
+ case LDAP_OBJECT_CLASS_VIOLATION:
+ case LDAP_NOT_ALLOWED_ON_NONLEAF:
+ case LDAP_NOT_ALLOWED_ON_RDN:
+ case LDAP_ALREADY_EXISTS:
+ case LDAP_NO_OBJECT_CLASS_MODS:
+ return_value = PR_TRUE;
+ break;
+
+ /* Cases where we stop and retry */
+ case LDAP_OPERATIONS_ERROR:
+ case LDAP_PROTOCOL_ERROR:
+ case LDAP_TIMELIMIT_EXCEEDED:
+ case LDAP_SIZELIMIT_EXCEEDED:
+ case LDAP_STRONG_AUTH_NOT_SUPPORTED:
+ case LDAP_STRONG_AUTH_REQUIRED:
+ case LDAP_PARTIAL_RESULTS:
+ case LDAP_REFERRAL:
+ case LDAP_ADMINLIMIT_EXCEEDED:
+ case LDAP_UNAVAILABLE_CRITICAL_EXTENSION:
+ case LDAP_CONFIDENTIALITY_REQUIRED:
+ case LDAP_SASL_BIND_IN_PROGRESS:
+ case LDAP_INAPPROPRIATE_MATCHING:
+ case LDAP_ALIAS_PROBLEM:
+ case LDAP_ALIAS_DEREF_PROBLEM:
+ case LDAP_INAPPROPRIATE_AUTH:
+ case LDAP_INVALID_CREDENTIALS:
+ case LDAP_BUSY:
+ case LDAP_UNAVAILABLE:
+ case LDAP_UNWILLING_TO_PERFORM:
+ case LDAP_LOOP_DETECT:
+ case LDAP_SORT_CONTROL_MISSING:
+ case LDAP_INDEX_RANGE_ERROR:
+ case LDAP_RESULTS_TOO_LARGE:
+ case LDAP_AFFECTS_MULTIPLE_DSAS:
+ case LDAP_OTHER:
+ case LDAP_SERVER_DOWN:
+ case LDAP_LOCAL_ERROR:
+ case LDAP_ENCODING_ERROR:
+ case LDAP_DECODING_ERROR:
+ case LDAP_TIMEOUT:
+ case LDAP_AUTH_UNKNOWN:
+ case LDAP_FILTER_ERROR:
+ case LDAP_USER_CANCELLED:
+ case LDAP_PARAM_ERROR:
+ case LDAP_NO_MEMORY:
+ case LDAP_CONNECT_ERROR:
+ case LDAP_NOT_SUPPORTED:
+ case LDAP_CONTROL_NOT_FOUND:
+ case LDAP_NO_RESULTS_RETURNED:
+ case LDAP_MORE_RESULTS_TO_RETURN:
+ case LDAP_CLIENT_LOOP:
+ case LDAP_REFERRAL_LIMIT_EXCEEDED:
+ return_value = PR_FALSE;
+ break;
+ }
+ return return_value;
+}
+
+/* this function converts a state to its name - for debug output */
+static const char*
+state2name (int state)
+{
+ switch (state)
+ {
+ case STATE_START: return "start";
+ case STATE_WAIT_WINDOW_OPEN: return "wait_for_window_to_open";
+ case STATE_WAIT_CHANGES: return "wait_for_changes";
+ case STATE_READY_TO_ACQUIRE: return "ready_to_acquire_replica";
+ case STATE_BACKOFF_START: return "start_backoff";
+ case STATE_BACKOFF: return "backoff";
+ case STATE_SENDING_UPDATES: return "sending_updates";
+ case STATE_STOP_FATAL_ERROR: return "stop_fatal_error";
+ case STATE_STOP_FATAL_ERROR_PART2: return "stop_fatal_error";
+ case STATE_STOP_NORMAL_TERMINATION: return "stop_normal_termination";
+ default: return "invalid_state";
+ }
+}
+
+/* this function convert s an event to its name - for debug output */
+static const char*
+event2name (int event)
+{
+ switch (event)
+ {
+ case EVENT_WINDOW_OPENED: return "update_window_opened";
+ case EVENT_WINDOW_CLOSED: return "update_window_closed";
+ case EVENT_TRIGGERING_CRITERIA_MET: return "data_modified";
+ case EVENT_BACKOFF_EXPIRED: return "backoff_timer_expired";
+ case EVENT_REPLICATE_NOW: return "replicate_now";
+ case EVENT_PROTOCOL_SHUTDOWN: return "protocol_shutdown";
+ case EVENT_AGMT_CHANGED: return "agreement_changed";
+ default: return "invalid_event";
+ }
+}
+
+static const char*
+op2string(int op)
+{
+ switch (op) {
+ case SLAPI_OPERATION_ADD:
+ return "add";
+ case SLAPI_OPERATION_MODIFY:
+ return "modify";
+ case SLAPI_OPERATION_DELETE:
+ return "delete";
+ case SLAPI_OPERATION_MODRDN:
+ return "rename";
+ case SLAPI_OPERATION_EXTENDED:
+ return "extended";
+ }
+
+ return "unknown";
+}
diff --git a/ldap/servers/plugins/replication/repl5_init.c b/ldap/servers/plugins/replication/repl5_init.c
new file mode 100644
index 00000000..eae3b238
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl5_init.c
@@ -0,0 +1,572 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+/*
+ repl5_init.c - plugin initialization functions
+*/
+
+/*
+ * Add an entry like the following to dse.ldif to enable this plugin:
+
+dn: cn=Multi-Master Replication Plugin,cn=plugins,cn=config
+objectclass: top
+objectclass: nsSlapdPlugin
+objectclass: extensibleObject
+cn: Legacy Replication Plugin
+nsslapd-pluginpath: /export2/servers/Hydra-supplier/lib/replication-plugin.so
+nsslapd-plugininitfunc: replication_multimaster_plugin_init
+nsslapd-plugintype: object
+nsslapd-pluginenabled: on
+nsslapd-plugin-depends-on-type: database
+nsslapd-plugin-depends-on-named: Class of Service
+nsslapd-pluginid: replication-multimaster
+nsslapd-pluginversion: 5.0b1
+nsslapd-pluginvendor: Netscape Communications
+nsslapd-plugindescription: Multi-Master Replication Plugin
+
+*/
+
+#include "slapi-plugin.h"
+#include "repl.h"
+#include "repl5.h"
+#include "cl5.h" /* changelog interface */
+#include "dirver.h"
+#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */
+
+/* #ifdef _WIN32
+int *module_ldap_debug = 0;
+
+void plugin_init_debug_level(int *level_ptr)
+{
+ module_ldap_debug = level_ptr;
+}
+#endif*/
+
+#define NSDS_REPL_NAME_PREFIX "Netscape Replication"
+
+static char *start_oid_list[] = {
+ REPL_START_NSDS50_REPLICATION_REQUEST_OID,
+ NULL
+};
+static char *start_name_list[] = {
+ NSDS_REPL_NAME_PREFIX " Start Session",
+ NULL
+};
+static char *end_oid_list[] = {
+ REPL_END_NSDS50_REPLICATION_REQUEST_OID,
+ NULL
+};
+static char *end_name_list[] = {
+ NSDS_REPL_NAME_PREFIX " End Session",
+ NULL
+};
+static char *total_oid_list[] = {
+ REPL_NSDS50_REPLICATION_ENTRY_REQUEST_OID,
+ NULL
+};
+static char *total_name_list[] = {
+ NSDS_REPL_NAME_PREFIX " Total Update Entry",
+ NULL
+};
+static char *response_oid_list[] = {
+ REPL_NSDS50_REPLICATION_RESPONSE_OID,
+ NULL
+};
+static char *response_name_list[] = {
+ NSDS_REPL_NAME_PREFIX " Response",
+ NULL
+};
+
+/* List of plugin identities for every plugin registered. Plugin identity
+ is passed by the server in the plugin init function and must be supplied
+ by the plugin to all internal operations it initiates
+ */
+
+/* ----------------------------- Multi-Master Replication Plugin */
+
+static Slapi_PluginDesc multimasterdesc = {"replication-multimaster", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Multi-master Replication Plugin"};
+static Slapi_PluginDesc multimasterpreopdesc = {"replication-multimaster-preop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Multi-master replication pre-operation plugin"};
+static Slapi_PluginDesc multimasterpostopdesc = {"replication-multimaster-postop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Multi-master replication post-operation plugin"};
+static Slapi_PluginDesc multimasterinternalpreopdesc = {"replication-multimaster-internalpreop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Multi-master replication internal pre-operation plugin"};
+static Slapi_PluginDesc multimasterinternalpostopdesc = {"replication-multimaster-internalpostop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Multimaster replication internal post-operation plugin"};
+static Slapi_PluginDesc multimasterbepreopdesc = {"replication-multimaster-bepreop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Multimaster replication bepre-operation plugin"};
+static Slapi_PluginDesc multimasterbepostopdesc = {"replication-multimaster-bepostop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Multimaster replication bepost-operation plugin"};
+static Slapi_PluginDesc multimasterextopdesc = { "replication-multimaster-extop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Multimaster replication extended-operation plugin" };
+
+static int multimaster_stopped_flag; /* A flag which is set when all the plugin threads are to stop */
+static int multimaster_started_flag = 0;
+
+/* Thread private data and interface */
+static PRUintn thread_private_agmtname; /* thread private index for logging*/
+static PRUintn thread_private_cache;
+
+char*
+get_thread_private_agmtname()
+{
+ char *agmtname = NULL;
+ if (thread_private_agmtname)
+ agmtname = PR_GetThreadPrivate(thread_private_agmtname);
+ return (agmtname ? agmtname : "");
+}
+
+void
+set_thread_private_agmtname(const char *agmtname)
+{
+ if (thread_private_agmtname)
+ PR_SetThreadPrivate(thread_private_agmtname, (void *)agmtname);
+}
+
+void*
+get_thread_private_cache ()
+{
+ void *buf = NULL;
+ if ( thread_private_cache )
+ buf = PR_GetThreadPrivate ( thread_private_cache );
+ return buf;
+}
+
+void
+set_thread_private_cache ( void *buf )
+{
+ if ( thread_private_cache )
+ PR_SetThreadPrivate ( thread_private_cache, buf );
+}
+
+char*
+get_repl_session_id (Slapi_PBlock *pb, char *idstr, CSN **csn)
+{
+ int connid=-1, opid=-1;
+ CSN *opcsn;
+ char opcsnstr[CSN_STRSIZE];
+
+ *idstr = '\0';
+ opcsn = NULL;
+ opcsnstr[0] = '\0';
+
+ if (pb) {
+ Slapi_Operation *op;
+ slapi_pblock_get (pb, SLAPI_OPERATION_ID, &opid);
+ /* Avoid "Connection is NULL and hence cannot access SLAPI_CONN_ID" */
+ if (opid) {
+ slapi_pblock_get (pb, SLAPI_CONN_ID, &connid);
+ sprintf (idstr, "conn=%d op=%d", connid, opid);
+ }
+
+ slapi_pblock_get ( pb, SLAPI_OPERATION, &op );
+ opcsn = operation_get_csn (op);
+ if (opcsn) {
+ csn_as_string (opcsn, PR_FALSE, opcsnstr);
+ strcat (idstr, " csn=");
+ strcat (idstr, opcsnstr);
+ }
+ }
+ if (csn) {
+ *csn = opcsn;
+ }
+ return idstr;
+}
+
+
+int
+multimaster_preop_init( Slapi_PBlock *pb )
+{
+ int rc= 0; /* OK */
+
+ if( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&multimasterpreopdesc ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_BIND_FN, (void *) multimaster_preop_bind ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_ADD_FN, (void *) multimaster_preop_add ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_DELETE_FN, (void *) multimaster_preop_delete ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_MODIFY_FN, (void *) multimaster_preop_modify ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_MODRDN_FN, (void *) multimaster_preop_modrdn ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_SEARCH_FN, (void *) multimaster_preop_search ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_COMPARE_FN, (void *) multimaster_preop_compare ) != 0)
+ {
+ slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "multimaster_preop_init failed\n" );
+ rc= -1;
+ }
+ return rc;
+}
+
+int
+multimaster_postop_init( Slapi_PBlock *pb )
+{
+ int rc= 0; /* OK */
+
+ if( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&multimasterpostopdesc ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_POST_BIND_FN, (void *) multimaster_postop_bind ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_POST_ADD_FN, (void *) multimaster_postop_add ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_POST_DELETE_FN, (void *) multimaster_postop_delete ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_POST_MODIFY_FN, (void *) multimaster_postop_modify ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_POST_MODRDN_FN, (void *) multimaster_postop_modrdn ) != 0 )
+ {
+ slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "multimaster_postop_init failed\n" );
+ rc= -1;
+ }
+
+ return rc;
+}
+
+int
+multimaster_internalpreop_init( Slapi_PBlock *pb )
+{
+ int rc= 0; /* OK */
+
+ if( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&multimasterinternalpreopdesc ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_PRE_ADD_FN, (void *) multimaster_preop_add ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_PRE_DELETE_FN, (void *) multimaster_preop_delete ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_PRE_MODIFY_FN, (void *) multimaster_preop_modify ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_PRE_MODRDN_FN, (void *) multimaster_preop_modrdn ) != 0 )
+ {
+ slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "multimaster_internalpreop_init failed\n" );
+ rc= -1;
+ }
+ return rc;
+}
+
+int
+multimaster_internalpostop_init( Slapi_PBlock *pb )
+{
+ int rc= 0; /* OK */
+
+ if( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&multimasterinternalpostopdesc ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_POST_ADD_FN, (void *) multimaster_postop_add ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_POST_DELETE_FN, (void *) multimaster_postop_delete ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_POST_MODIFY_FN, (void *) multimaster_postop_modify ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_POST_MODRDN_FN, (void *) multimaster_postop_modrdn ) != 0 )
+ {
+ slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "multimaster_internalpostop_init failed\n" );
+ rc= -1;
+ }
+
+ return rc;
+}
+
+int
+multimaster_bepreop_init( Slapi_PBlock *pb )
+{
+ int rc= 0; /* OK */
+
+ if( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&multimasterbepreopdesc ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_BE_PRE_ADD_FN, (void *) multimaster_bepreop_add ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_BE_PRE_DELETE_FN, (void *) multimaster_bepreop_delete ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_BE_PRE_MODIFY_FN, (void *) multimaster_bepreop_modify ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_BE_PRE_MODRDN_FN, (void *) multimaster_bepreop_modrdn ) != 0 )
+ {
+ slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "multimaster_bepreop_init failed\n" );
+ rc= -1;
+ }
+
+ return rc;
+}
+
+int
+multimaster_bepostop_init( Slapi_PBlock *pb )
+{
+ int rc= 0; /* OK */
+
+ if( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&multimasterbepostopdesc ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_BE_POST_MODRDN_FN, (void *) multimaster_bepostop_modrdn ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_BE_POST_DELETE_FN, (void *) multimaster_bepostop_delete ) != 0 )
+ {
+ slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "multimaster_bepostop_init failed\n" );
+ rc= -1;
+ }
+
+ return rc;
+}
+
+int
+multimaster_start_extop_init( Slapi_PBlock *pb )
+{
+ int rc= 0; /* OK */
+
+ if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&multimasterextopdesc ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_OIDLIST, (void *)start_oid_list ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_NAMELIST, (void *)start_name_list ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_FN, (void *)multimaster_extop_StartNSDS50ReplicationRequest ))
+ {
+ slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "multimaster_start_extop_init (StartNSDS50ReplicationRequest) failed\n" );
+ rc= -1;
+ }
+
+
+ return rc;
+}
+
+
+int
+multimaster_end_extop_init( Slapi_PBlock *pb )
+{
+ int rc= 0; /* OK */
+
+
+ if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&multimasterextopdesc ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_OIDLIST, (void *)end_oid_list ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_NAMELIST, (void *)end_name_list ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_FN, (void *)multimaster_extop_EndNSDS50ReplicationRequest ))
+ {
+ slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "multimaster_end_extop_init (EndNSDS50ReplicationRequest) failed\n" );
+ rc= -1;
+ }
+
+ return rc;
+}
+
+
+int
+multimaster_total_extop_init( Slapi_PBlock *pb )
+{
+ int rc= 0; /* OK */
+ void *identity = NULL;
+
+ /* get plugin identity and store it to pass to internal operations */
+ slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &identity);
+ PR_ASSERT (identity);
+
+ if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&multimasterextopdesc ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_OIDLIST, (void *)total_oid_list ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_NAMELIST, (void *)total_name_list ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_FN, (void *)multimaster_extop_NSDS50ReplicationEntry ))
+ {
+ slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "multimaster_start_extop_init (NSDS50ReplicationEntry failed\n" );
+ rc= -1;
+ }
+
+ return rc;
+}
+
+int
+multimaster_response_extop_init( Slapi_PBlock *pb )
+{
+ int rc= 0; /* OK */
+ void *identity = NULL;
+
+ /* get plugin identity and store it to pass to internal operations */
+ slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &identity);
+ PR_ASSERT (identity);
+
+ if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&multimasterextopdesc ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_OIDLIST, (void *)response_oid_list ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_NAMELIST, (void *)response_name_list ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_FN, (void *)extop_noop ))
+ {
+ slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "multimaster_start_extop_init (NSDS50ReplicationResponse failed\n" );
+ rc= -1;
+ }
+
+ return rc;
+}
+
+
+static PRBool
+check_for_ldif_dump(Slapi_PBlock *pb)
+{
+ int i;
+ int argc;
+ char **argv;
+ PRBool return_value = PR_FALSE;
+
+ slapi_pblock_get( pb, SLAPI_ARGC, &argc);
+ slapi_pblock_get( pb, SLAPI_ARGV, &argv);
+
+ for (i = 1; i < argc && !return_value; i++)
+ {
+ if (strcmp(argv[i], "db2ldif") == 0)
+ {
+ return_value = PR_TRUE;
+ }
+ }
+ return return_value;
+}
+
+
+static PRBool is_ldif_dump = PR_FALSE;
+
+int
+multimaster_start( Slapi_PBlock *pb )
+{
+ int rc= 0; /* OK */
+
+ if (!multimaster_started_flag)
+ {
+ /* Initialize thread private data for logging. Ignore if fails */
+ PR_NewThreadPrivateIndex (&thread_private_agmtname, NULL);
+ PR_NewThreadPrivateIndex (&thread_private_cache, NULL);
+
+ /* Decode the command line args to see if we're dumping to LDIF */
+ is_ldif_dump = check_for_ldif_dump(pb);
+
+ /* allow online replica configuration */
+ rc = replica_config_init ();
+ if (rc != 0)
+ goto out;
+
+ slapi_register_supported_control(REPL_NSDS50_UPDATE_INFO_CONTROL_OID,
+ SLAPI_OPERATION_ADD | SLAPI_OPERATION_DELETE |
+ SLAPI_OPERATION_MODIFY | SLAPI_OPERATION_MODDN);
+
+ /* Stash away our partial URL, used in RUVs */
+ rc = multimaster_set_local_purl();
+ if (rc != 0)
+ goto out;
+
+ /* Initialise support for cn=monitor */
+ rc = repl_monitor_init();
+ if (rc != 0)
+ goto out;
+
+ /* initialize name hash */
+ rc = replica_init_name_hash ();
+ if (rc != 0)
+ goto out;
+
+ /* initialize dn hash */
+ rc = replica_init_dn_hash ();
+ if (rc != 0)
+ goto out;
+
+ /* create replicas */
+ multimaster_mtnode_construct_replicas ();
+
+ /* Initialise the 5.0 Changelog */
+ rc = changelog5_init();
+ if (rc != 0)
+ goto out;
+
+ /* Initialize the replication agreements, unless we're dumping LDIF */
+ if (!is_ldif_dump)
+ {
+ rc = agmtlist_config_init();
+ if (rc != 0)
+ goto out;
+ }
+
+ /* check if the replica's data was reloaded offline and we need
+ to reinitialize replica's changelog. This should be done
+ after the changelog is initialized */
+
+ replica_enumerate_replicas (replica_check_for_data_reload, NULL);
+
+ /* register to be notified when backend state changes */
+ slapi_register_backend_state_change((void *)multimaster_be_state_change,
+ multimaster_be_state_change);
+
+ multimaster_started_flag = 1;
+ multimaster_stopped_flag = 0;
+ }
+out:
+ return rc;
+}
+
+int
+multimaster_stop( Slapi_PBlock *pb )
+{
+ int rc= 0; /* OK */
+
+ if (!multimaster_stopped_flag)
+ {
+ if (!is_ldif_dump)
+ {
+ agmtlist_shutdown(); /* Shut down replication agreements */
+ }
+
+ /* unregister backend state change notification */
+ slapi_unregister_backend_state_change((void *)multimaster_be_state_change);
+
+ changelog5_cleanup(); /* Shut down the changelog */
+ multimaster_mtnode_extension_destroy(); /* Destroy mapping tree node exts */
+ replica_destroy_name_hash(); /* destroy the hash and its remaining content */
+ replica_config_destroy (); /* Destroy replica config info */
+ multimaster_stopped_flag = 1;
+ /* JCMREPL - Wait for all our threads to stop */
+ /* JCMREPL - Shut down the replication plugin */
+ /* JCMREPL - Mark all the replication plugin interfaces at not enabled. */
+ }
+ return rc;
+}
+
+
+PRBool
+multimaster_started()
+{
+ return(multimaster_started_flag != 0);
+}
+
+
+/*
+ * Initialize the multimaster replication plugin.
+ */
+int replication_multimaster_plugin_init(Slapi_PBlock *pb)
+{
+ static int multimaster_initialised= 0;
+ int rc= 0; /* OK */
+ void *identity = NULL;
+
+ slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &identity);
+ PR_ASSERT (identity);
+ repl_set_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION, identity);
+
+ /* need the repl plugin path for the chain on update function */
+/* slapi_pblock_get(pb, SLAPI_ADD_ENTRY, &entry);
+ PR_ASSERT(entry);
+ path = slapi_entry_attr_get_charptr(entry, "nsslapd-pluginpath");
+ repl_set_repl_plugin_path(path);
+ slapi_ch_free_string(&path);
+*/
+ multimaster_mtnode_extension_init ();
+
+ if(config_is_slapd_lite())
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name,
+ "replication plugin not approved for restricted"
+ " mode Directory Server.\n" );
+ rc= -1;
+ }
+ if(rc==0 && !multimaster_initialised)
+ {
+ /* initialize replica hash - has to be done before mapping tree is
+ initialized so we can't do it in the start function */
+
+ if (rc != 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "replication_multimaster_plugin_init: failed to initialize replica hash\n");
+ return -1;
+ }
+
+ /* Initialize extensions */
+ repl_con_init_ext();
+ repl_sup_init_ext();
+
+ rc= slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 );
+ rc= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&multimasterdesc );
+ rc= slapi_pblock_set( pb, SLAPI_PLUGIN_START_FN, (void *) multimaster_start );
+ rc= slapi_pblock_set( pb, SLAPI_PLUGIN_CLOSE_FN, (void *) multimaster_stop );
+
+ /* Register the plugin interfaces we implement */
+ rc= slapi_register_plugin("preoperation", 1 /* Enabled */, "multimaster_preop_init", multimaster_preop_init, "Multimaster replication preoperation plugin", NULL, identity);
+ rc= slapi_register_plugin("postoperation", 1 /* Enabled */, "multimaster_postop_init", multimaster_postop_init, "Multimaster replication postoperation plugin", NULL, identity);
+ rc= slapi_register_plugin("bepreoperation", 1 /* Enabled */, "multimaster_bepreop_init", multimaster_bepreop_init, "Multimaster replication bepreoperation plugin", NULL, identity);
+ rc= slapi_register_plugin("bepostoperation", 1 /* Enabled */, "multimaster_bepostop_init", multimaster_bepostop_init, "Multimaster replication bepostoperation plugin", NULL, identity);
+ rc= slapi_register_plugin("internalpreoperation", 1 /* Enabled */, "multimaster_internalpreop_init", multimaster_internalpreop_init, "Multimaster replication internal preoperation plugin", NULL, identity);
+ rc= slapi_register_plugin("internalpostoperation", 1 /* Enabled */, "multimaster_internalpostop_init", multimaster_internalpostop_init, "Multimaster replication internal postoperation plugin", NULL, identity);
+ rc= slapi_register_plugin("extendedop", 1 /* Enabled */, "multimaster_start_extop_init", multimaster_start_extop_init, "Multimaster replication start extended operation plugin", NULL, identity);
+ rc= slapi_register_plugin("extendedop", 1 /* Enabled */, "multimaster_end_extop_init", multimaster_end_extop_init, "Multimaster replication end extended operation plugin", NULL, identity);
+ rc= slapi_register_plugin("extendedop", 1 /* Enabled */, "multimaster_total_extop_init", multimaster_total_extop_init, "Multimaster replication total update extended operation plugin", NULL, identity);
+ rc= slapi_register_plugin("extendedop", 1 /* Enabled */, "multimaster_response_extop_init", multimaster_response_extop_init, "Multimaster replication extended response plugin", NULL, identity);
+ }
+ return rc;
+}
diff --git a/ldap/servers/plugins/replication/repl5_mtnode_ext.c b/ldap/servers/plugins/replication/repl5_mtnode_ext.c
new file mode 100644
index 00000000..e677927c
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl5_mtnode_ext.c
@@ -0,0 +1,194 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+/* repl5_replica.c */
+
+#include "repl.h" /* ONREPL - this is bad */
+#include "repl5.h"
+#include "cl5_api.h"
+
+/* global data */
+static DataList *root_list;
+
+/*
+ * Mapping tree node extension management. Node stores replica object
+ */
+
+void
+multimaster_mtnode_extension_init ()
+{
+ /* Initialize list that store node roots. It is used during
+ plugin startup to create replica objects */
+ root_list = dl_new ();
+ dl_init (root_list, 0);
+}
+
+void
+multimaster_mtnode_extension_destroy ()
+{
+ dl_cleanup (root_list, (FREEFN)slapi_sdn_free);
+ dl_free (&root_list);
+}
+
+/* This function loops over the list of node roots, constructing replica objects
+ where exist */
+void
+multimaster_mtnode_construct_replicas ()
+{
+ Slapi_DN *root;
+ int cookie;
+ Replica *r;
+ mapping_tree_node *mtnode;
+ multimaster_mtnode_extension *ext;
+
+ for (root = dl_get_first (root_list, &cookie); root;
+ root = dl_get_next (root_list, &cookie))
+ {
+ r = replica_new(root);
+ if (r)
+ {
+
+ mtnode = slapi_get_mapping_tree_node_by_dn(root);
+ if (mtnode == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "multimaster_mtnode_construct_replicas: "
+ "failed to locate mapping tree node for %s\n",
+ slapi_sdn_get_dn (root));
+ continue;
+ }
+
+ ext = (multimaster_mtnode_extension *)repl_con_get_ext (REPL_CON_EXT_MTNODE, mtnode);
+ if (ext == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "multimaster_mtnode_construct_replicas: "
+ "failed to locate replication extension of mapping tree node for %s\n",
+ slapi_sdn_get_dn (root));
+ continue;
+ }
+
+ ext->replica = object_new(r, replica_destroy);
+ if (replica_add_by_name (replica_get_name (r), ext->replica) != 0)
+ {
+ object_release (ext->replica);
+ ext->replica = NULL;
+ }
+ }
+ }
+}
+
+void *
+multimaster_mtnode_extension_constructor (void *object, void *parent)
+{
+ mapping_tree_node *node;
+ const Slapi_DN *root;
+ multimaster_mtnode_extension *ext;
+
+ ext = (multimaster_mtnode_extension *)slapi_ch_calloc (1, sizeof (multimaster_mtnode_extension));
+
+ node = (mapping_tree_node *)object;
+
+ /* replica can be attached only to local public data */
+ if (slapi_mapping_tree_node_is_set (node, SLAPI_MTN_LOCAL) &&
+ !slapi_mapping_tree_node_is_set (node, SLAPI_MTN_PRIVATE))
+ {
+ root = slapi_get_mapping_tree_node_root (node);
+ /* ONREPL - we don't create replica object here because
+ we can't fully initialize replica here since backends
+ are not yet started. Instead, replica objects are created
+ during replication plugin startup */
+ if (root)
+ {
+ /* for now just store node root in the root list */
+ dl_add (root_list, slapi_sdn_dup (root));
+ }
+ }
+
+ return ext;
+}
+
+void
+multimaster_mtnode_extension_destructor (void* ext, void *object, void *parent)
+{
+ if (ext)
+ {
+ multimaster_mtnode_extension *mtnode_ext = (multimaster_mtnode_extension *)ext;
+ if (mtnode_ext->replica)
+ {
+ object_release (mtnode_ext->replica);
+ mtnode_ext->replica = NULL;
+ }
+ }
+}
+
+Object *
+replica_get_replica_from_dn (const Slapi_DN *dn)
+{
+ mapping_tree_node *mtnode;
+ multimaster_mtnode_extension *ext;
+
+ if (dn == NULL)
+ return NULL;
+
+ mtnode = slapi_get_mapping_tree_node_by_dn(dn);
+ if (mtnode == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "replica_get_replica_from_dn: "
+ "failed to locate mapping tree node for %s\n",
+ slapi_sdn_get_dn (dn));
+ return NULL;
+ }
+
+ ext = (multimaster_mtnode_extension *)repl_con_get_ext (REPL_CON_EXT_MTNODE, mtnode);
+ if (ext == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "replica_get_replica_from_dn: "
+ "failed to locate replication extension of mapping tree node for %s\n",
+ slapi_sdn_get_dn (dn));
+ return NULL;
+ }
+
+ if (ext->replica)
+ object_acquire (ext->replica);
+
+ return ext->replica;
+}
+
+Object *replica_get_replica_for_op (Slapi_PBlock *pb)
+{
+ char *dn;
+ Slapi_DN *sdn;
+ Object *repl_obj = NULL;
+
+ if (pb)
+ {
+ /* get replica generation for this operation */
+ slapi_pblock_get (pb, SLAPI_TARGET_DN, &dn);
+ sdn = slapi_sdn_new_dn_byref(dn);
+ repl_obj = replica_get_replica_from_dn (sdn);
+
+ slapi_sdn_free (&sdn);
+ }
+
+ return repl_obj;
+}
+
+Object *replica_get_for_backend (const char *be_name)
+{
+ Slapi_Backend *be;
+ const Slapi_DN *suffix;
+ Object *r_obj;
+
+ be = slapi_be_select_by_instance_name(be_name);
+ if (NULL == be)
+ return NULL;
+
+ suffix = slapi_be_getsuffix(be, 0);
+
+ r_obj = replica_get_replica_from_dn (suffix);
+
+ return r_obj;
+}
diff --git a/ldap/servers/plugins/replication/repl5_plugins.c b/ldap/servers/plugins/replication/repl5_plugins.c
new file mode 100644
index 00000000..afee321a
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl5_plugins.c
@@ -0,0 +1,1416 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+/*
+ * repl5_consumer.c - consumer plugin functions
+ */
+
+/*
+ * LDAP Data Model Constraints...
+ *
+ * 1) Parent entry must exist.
+ * 2) RDN must be unique.
+ * 3) RDN components must be attribute values.
+ * 4) Must conform to the schema constraints - Single Valued Attributes.
+ * 5) Must conform to the schema constraints - Required Attributes.
+ * 6) Must conform to the schema constraints - No Extra Attributes.
+ * 7) Duplicate attribute values are not permited.
+ * 8) Cycle in the ancestry graph not permitted.
+ *
+ * Update Resolution Procedures...
+ * 1) ...
+ * 2) Add the UniqueID to the RDN.
+ * 3) Remove the not present RDN component.
+ * Use the UniqueID if the RDN becomes empty.
+ * 4) Keep the most recent value.
+ * 5) Ignore.
+ * 6) Ignore.
+ * 7) Keep the largest CSN for the duplicate value.
+ * 8) Don't check for this.
+ */
+
+#include "repl5.h"
+#include "repl.h"
+#include "cl5_api.h"
+#include "urp.h"
+
+static char *local_purl = NULL;
+static char *purl_attrs[] = {"nsslapd-localhost", "nsslapd-port", "nsslapd-secureport", NULL};
+
+/* Forward declarations */
+static void copy_operation_parameters(Slapi_PBlock *pb);
+static int write_changelog_and_ruv(Slapi_PBlock *pb);
+static int process_postop (Slapi_PBlock *pb);
+static int cancel_opcsn (Slapi_PBlock *pb);
+static int ruv_tombstone_op (Slapi_PBlock *pb);
+static PRBool process_operation (Slapi_PBlock *pb, const CSN *csn);
+static PRBool is_mmr_replica (Slapi_PBlock *pb);
+static const char *replica_get_purl_for_op (const Replica *r, Slapi_PBlock *pb, const CSN *opcsn);
+static void strip_legacy_info (slapi_operation_parameters *op_params);
+static void close_changelog_for_replica (Object *r_obj);
+static void process_new_ruv_for_replica (Replica *r);
+
+/*
+ * XXXggood - what to do if both ssl and non-ssl ports available? How
+ * do you know which to use? Offer a choice in replication config?
+ */
+int
+multimaster_set_local_purl()
+{
+ int rc = 0;
+ Slapi_Entry **entries;
+ Slapi_PBlock *pb = NULL;
+
+ pb = slapi_pblock_new ();
+
+ slapi_search_internal_set_pb (pb, "cn=config", LDAP_SCOPE_BASE,
+ "objectclass=*", purl_attrs, 0, NULL, NULL,
+ repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0);
+ slapi_search_internal_pb (pb);
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
+ if (rc != 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "multimaster_set_local_purl: "
+ "unable to read server configuration: error %d\n", rc);
+ }
+ else
+ {
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries);
+ if (NULL == entries || NULL == entries[0])
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "multimaster_set_local_purl: "
+ "server configuration missing\n");
+ rc = -1;
+ }
+ else
+ {
+ char *host = slapi_entry_attr_get_charptr(entries[0], "nsslapd-localhost");
+ char *port = slapi_entry_attr_get_charptr(entries[0], "nsslapd-port");
+ char *sslport = slapi_entry_attr_get_charptr(entries[0], "nsslapd-secureport");
+ if (host == NULL || ((port == NULL && sslport == NULL)))
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "multimaster_set_local_purl: invalid server "
+ "configuration\n");
+ }
+ else
+ {
+ int len = 0;
+ char *patt = "ldap://%s:%s";
+ len += strlen(host);
+ len += strlen(port);
+ len += strlen(patt);
+ len++; /* for \0 */
+ local_purl = slapi_ch_malloc(len);
+ sprintf(local_purl, patt, host, port);
+ }
+
+ /* slapi_ch_free acceptS NULL pointer */
+ slapi_ch_free ((void**)&host);
+ slapi_ch_free ((void**)&port);
+ slapi_ch_free ((void**)&sslport);
+ }
+ }
+ slapi_free_search_results_internal(pb);
+ slapi_pblock_destroy (pb);
+
+ return rc;
+}
+
+
+const char *
+multimaster_get_local_purl()
+{
+ return local_purl;
+}
+
+
+/* ================= Multimaster Pre-Op Plugin Points ================== */
+
+
+int
+multimaster_preop_bind (Slapi_PBlock *pb)
+{
+ return 0;
+}
+
+int
+multimaster_preop_add (Slapi_PBlock *pb)
+{
+ Slapi_Operation *op;
+ int is_replicated_operation;
+ int is_fixup_operation;
+ int is_legacy_operation;
+
+ slapi_pblock_get(pb, SLAPI_OPERATION, &op);
+
+ /* If there is no replica or it is a legacy consumer - we don't need to continue.
+ Legacy plugin is handling 4.0 consumer code */
+ /* but if it is legacy, csngen_handler needs to be assigned here */
+ is_legacy_operation =
+ operation_is_flag_set(op,OP_FLAG_LEGACY_REPLICATION_DN);
+ if (is_legacy_operation)
+ {
+ copy_operation_parameters(pb);
+ slapi_operation_set_csngen_handler(op,
+ (void*)replica_generate_next_csn);
+ return 0;
+ }
+
+ if (!is_mmr_replica (pb))
+ {
+ copy_operation_parameters(pb);
+ return 0;
+ }
+
+ is_replicated_operation= operation_is_flag_set(op,OP_FLAG_REPLICATED);
+ is_fixup_operation= operation_is_flag_set(op,OP_FLAG_REPL_FIXUP);
+
+ if(is_replicated_operation)
+ {
+ if(!is_fixup_operation)
+ {
+ LDAPControl **ctrlp;
+ char sessionid[REPL_SESSION_ID_SIZE];
+ get_repl_session_id (pb, sessionid, NULL);
+ slapi_pblock_get(pb, SLAPI_REQCONTROLS, &ctrlp);
+ if (NULL != ctrlp)
+ {
+ CSN *csn = NULL;
+ char *target_uuid = NULL;
+ char *superior_uuid= NULL;
+ int drc = decode_NSDS50ReplUpdateInfoControl(ctrlp, &target_uuid, &superior_uuid, &csn, NULL /* modrdn_mods */);
+ if (-1 == drc)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, REPLICATION_SUBSYSTEM,
+ "%s An error occurred while decoding the replication update "
+ "control - Add\n", sessionid);
+ }
+ else if (1 == drc)
+ {
+ /*
+ * For add operations, we just set the operation csn. The entry's
+ * uniqueid should already be an attribute of the replicated entry.
+ */
+ struct slapi_operation_parameters *op_params;
+
+ /* we don't want to process replicated operations with csn smaller
+ than the corresponding csn in the consumer's ruv */
+ if (!process_operation (pb, csn))
+ {
+ slapi_send_ldap_result(pb, LDAP_SUCCESS, 0,
+ "replication operation not processed, replica unavailable "
+ "or csn ignored", 0, 0);
+ csn_free (&csn);
+ slapi_ch_free ((void**)&target_uuid);
+ slapi_ch_free ((void**)&superior_uuid);
+
+ return -1;
+ }
+
+ operation_set_csn(op, csn);
+ slapi_pblock_set( pb, SLAPI_TARGET_UNIQUEID, target_uuid);
+ slapi_pblock_get( pb, SLAPI_OPERATION_PARAMETERS, &op_params );
+ /* JCMREPL - Complain if there's no superior uuid */
+ op_params->p.p_add.parentuniqueid= superior_uuid; /* JCMREPL - Not very elegant */
+ /* JCMREPL - When do these things get free'd? */
+ if(target_uuid!=NULL)
+ {
+ /*
+ * Make sure that the Unique Identifier in the Control is the
+ * same as the one in the entry.
+ */
+ Slapi_Entry *addentry;
+ char *entry_uuid;
+ slapi_pblock_get( pb, SLAPI_ADD_ENTRY, &addentry );
+ entry_uuid= slapi_entry_attr_get_charptr( addentry, SLAPI_ATTR_UNIQUEID);
+ if(entry_uuid==NULL)
+ {
+ /* Odd that the entry doesn't have a Unique Identifier. But, we can fix it. */
+ slapi_entry_set_uniqueid(addentry, slapi_ch_strdup(target_uuid)); /* JCMREPL - strdup EVIL! There should be a uuid dup function. */
+ }
+ else
+ {
+ if(strcasecmp(entry_uuid,target_uuid)!=0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, REPLICATION_SUBSYSTEM,
+ "%s Replicated Add received with Control_UUID=%s and Entry_UUID=%s.\n",
+ sessionid, target_uuid,entry_uuid);
+ }
+
+ slapi_ch_free ((void**)&entry_uuid);
+ }
+ }
+ }
+ }
+ else
+ {
+ PR_ASSERT(0); /* JCMREPL - A Replicated Operation with no Repl Baggage control... What does that mean? */
+ }
+ }
+ else
+ {
+ /* Replicated & Fixup Operation */
+ }
+ }
+ else
+ {
+ slapi_operation_set_csngen_handler ( op, (void*)replica_generate_next_csn );
+ }
+
+ copy_operation_parameters(pb);
+
+ return 0;
+}
+
+int
+multimaster_preop_delete (Slapi_PBlock *pb)
+{
+ Slapi_Operation *op;
+ int is_replicated_operation;
+ int is_fixup_operation;
+ int is_legacy_operation;
+
+ slapi_pblock_get(pb, SLAPI_OPERATION, &op);
+
+ /* If there is no replica or it is a legacy consumer - we don't need to continue.
+ Legacy plugin is handling 4.0 consumer code */
+ /* but if it is legacy, csngen_handler needs to be assigned here */
+ is_legacy_operation =
+ operation_is_flag_set(op,OP_FLAG_LEGACY_REPLICATION_DN);
+ if (is_legacy_operation)
+ {
+ copy_operation_parameters(pb);
+ slapi_operation_set_replica_attr_handler ( op, (void*)replica_get_attr );
+ slapi_operation_set_csngen_handler(op,
+ (void*)replica_generate_next_csn);
+ return 0;
+ }
+
+ if (!is_mmr_replica (pb))
+ {
+ copy_operation_parameters(pb);
+ return 0;
+ }
+
+ is_replicated_operation= operation_is_flag_set(op,OP_FLAG_REPLICATED);
+ is_fixup_operation= operation_is_flag_set(op,OP_FLAG_REPL_FIXUP);
+
+ if(is_replicated_operation)
+ {
+ if(!is_fixup_operation)
+ {
+ LDAPControl **ctrlp;
+ char sessionid[REPL_SESSION_ID_SIZE];
+ get_repl_session_id (pb, sessionid, NULL);
+ slapi_pblock_get(pb, SLAPI_REQCONTROLS, &ctrlp);
+ if (NULL != ctrlp)
+ {
+ CSN *csn = NULL;
+ char *target_uuid = NULL;
+ int drc = decode_NSDS50ReplUpdateInfoControl(ctrlp, &target_uuid, NULL, &csn, NULL /* modrdn_mods */);
+ if (-1 == drc)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, REPLICATION_SUBSYSTEM,
+ "%s An error occurred while decoding the replication update "
+ "control - Delete\n", sessionid);
+ }
+ else if (1 == drc)
+ {
+ /* we don't want to process replicated operations with csn smaller
+ than the corresponding csn in the consumer's ruv */
+ if (!process_operation (pb, csn))
+ {
+ slapi_send_ldap_result(pb, LDAP_SUCCESS, 0,
+ "replication operation not processed, replica unavailable "
+ "or csn ignored", 0, 0);
+ slapi_log_error(SLAPI_LOG_REPL, REPLICATION_SUBSYSTEM,
+ "%s replication operation not processed, replica unavailable "
+ "or csn ignored\n", sessionid);
+ csn_free (&csn);
+ slapi_ch_free ((void**)&target_uuid);
+
+ return -1;
+ }
+
+ /*
+ * For delete operations, we pass the uniqueid of the deleted entry
+ * to the backend and let it sort out which entry to really delete.
+ * We also set the operation csn.
+ */
+ operation_set_csn(op, csn);
+ slapi_pblock_set(pb, SLAPI_TARGET_UNIQUEID, target_uuid);
+ }
+ }
+ else
+ {
+ PR_ASSERT(0); /* JCMREPL - A Replicated Operation with no Repl Baggage control... What does that mean? */
+ }
+ }
+ else
+ {
+ /* Replicated & Fixup Operation */
+ }
+ }
+ else
+ {
+ slapi_operation_set_csngen_handler ( op, (void*)replica_generate_next_csn );
+ }
+
+ copy_operation_parameters(pb);
+ slapi_operation_set_replica_attr_handler ( op, (void*)replica_get_attr );
+
+ return 0;
+}
+
+int
+multimaster_preop_modify (Slapi_PBlock *pb)
+{
+ Slapi_Operation *op;
+ int is_replicated_operation;
+ int is_fixup_operation;
+ int is_legacy_operation;
+
+ slapi_pblock_get(pb, SLAPI_OPERATION, &op);
+
+ /* If there is no replica or it is a legacy consumer - we don't need to continue.
+ Legacy plugin is handling 4.0 consumer code */
+ /* but if it is legacy, csngen_handler needs to be assigned here */
+ is_legacy_operation =
+ operation_is_flag_set(op,OP_FLAG_LEGACY_REPLICATION_DN);
+ if (is_legacy_operation)
+ {
+ copy_operation_parameters(pb);
+ slapi_operation_set_csngen_handler(op,
+ (void*)replica_generate_next_csn);
+ return 0;
+ }
+
+ if (!is_mmr_replica (pb))
+ {
+ copy_operation_parameters(pb);
+ return 0;
+ }
+
+ is_replicated_operation= operation_is_flag_set(op,OP_FLAG_REPLICATED);
+ is_fixup_operation= operation_is_flag_set(op,OP_FLAG_REPL_FIXUP);
+
+ if(is_replicated_operation)
+ {
+ if(!is_fixup_operation)
+ {
+ LDAPControl **ctrlp;
+ char sessionid[REPL_SESSION_ID_SIZE];
+ get_repl_session_id (pb, sessionid, NULL);
+ slapi_pblock_get(pb, SLAPI_REQCONTROLS, &ctrlp);
+ if (NULL != ctrlp)
+ {
+ CSN *csn = NULL;
+ char *target_uuid = NULL;
+ int drc = decode_NSDS50ReplUpdateInfoControl(ctrlp, &target_uuid, NULL, &csn, NULL /* modrdn_mods */);
+ if (-1 == drc)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, REPLICATION_SUBSYSTEM,
+ "%s An error occurred while decoding the replication update "
+ "control- Modify\n", sessionid);
+ }
+ else if (1 == drc)
+ {
+ /* we don't want to process replicated operations with csn smaller
+ than the corresponding csn in the consumer's ruv */
+ if (!process_operation (pb, csn))
+ {
+ slapi_send_ldap_result(pb, LDAP_SUCCESS, 0,
+ "replication operation not processed, replica unavailable "
+ "or csn ignored", 0, 0);
+ slapi_log_error(SLAPI_LOG_REPL, REPLICATION_SUBSYSTEM,
+ "%s replication operation not processed, replica unavailable "
+ "or csn ignored\n", sessionid);
+ csn_free (&csn);
+ slapi_ch_free ((void**)&target_uuid);
+
+ return -1;
+ }
+
+ /*
+ * For modify operations, we pass the uniqueid of the modified entry
+ * to the backend and let it sort out which entry to really modify.
+ * We also set the operation csn.
+ */
+ operation_set_csn(op, csn);
+ slapi_pblock_set(pb, SLAPI_TARGET_UNIQUEID, target_uuid);
+ }
+ }
+ else
+ {
+ PR_ASSERT(0); /* JCMREPL - A Replicated Operation with no Repl Baggage control... What does that mean? */
+ }
+ }
+ else
+ {
+ /* Replicated & Fixup Operation */
+ }
+ }
+ else
+ {
+ slapi_operation_set_csngen_handler ( op, (void*)replica_generate_next_csn );
+ }
+
+ copy_operation_parameters(pb);
+ return 0;
+}
+
+int
+multimaster_preop_modrdn (Slapi_PBlock *pb)
+{
+ Slapi_Operation *op;
+ int is_replicated_operation;
+ int is_fixup_operation;
+ int is_legacy_operation;
+
+ slapi_pblock_get(pb, SLAPI_OPERATION, &op);
+
+ /* If there is no replica or it is a legacy consumer - we don't need to continue.
+ Legacy plugin is handling 4.0 consumer code */
+ /* but if it is legacy, csngen_handler needs to be assigned here */
+ is_legacy_operation =
+ operation_is_flag_set(op,OP_FLAG_LEGACY_REPLICATION_DN);
+ if (is_legacy_operation)
+ {
+ copy_operation_parameters(pb);
+ slapi_operation_set_csngen_handler(op,
+ (void*)replica_generate_next_csn);
+ return 0;
+ }
+
+ if (!is_mmr_replica (pb))
+ {
+ copy_operation_parameters(pb);
+ return 0;
+ }
+
+ is_replicated_operation= operation_is_flag_set(op,OP_FLAG_REPLICATED);
+ is_fixup_operation= operation_is_flag_set(op,OP_FLAG_REPL_FIXUP);
+
+ if(is_replicated_operation)
+ {
+ if(!is_fixup_operation)
+ {
+ LDAPControl **ctrlp;
+ char sessionid[REPL_SESSION_ID_SIZE];
+ get_repl_session_id (pb, sessionid, NULL);
+ slapi_pblock_get(pb, SLAPI_REQCONTROLS, &ctrlp);
+ if (NULL != ctrlp)
+ {
+ CSN *csn = NULL;
+ char *target_uuid = NULL;
+ char *newsuperior_uuid = NULL;
+ LDAPMod **modrdn_mods = NULL;
+ int drc = decode_NSDS50ReplUpdateInfoControl(ctrlp, &target_uuid, &newsuperior_uuid,
+ &csn, &modrdn_mods);
+ if (-1 == drc)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, REPLICATION_SUBSYSTEM,
+ "%s An error occurred while decoding the replication update "
+ "control - ModRDN\n", sessionid);
+ }
+ else if (1 == drc)
+ {
+ /*
+ * For modrdn operations, we pass the uniqueid of the entry being
+ * renamed to the backend and let it sort out which entry to really
+ * rename. We also set the operation csn, and if the newsuperior_uuid
+ * was sent, we decode that as well.
+ */
+ struct slapi_operation_parameters *op_params;
+
+ /* we don't want to process replicated operations with csn smaller
+ than the corresponding csn in the consumer's ruv */
+ if (!process_operation (pb, csn))
+ {
+ slapi_send_ldap_result(pb, LDAP_SUCCESS, 0,
+ "replication operation not processed, replica unavailable "
+ "or csn ignored", 0, 0);
+ csn_free (&csn);
+ slapi_ch_free ((void**)&target_uuid);
+ slapi_ch_free ((void**)&newsuperior_uuid);
+ ldap_mods_free (modrdn_mods, 1);
+
+ return -1;
+ }
+
+ operation_set_csn(op, csn);
+ slapi_pblock_set(pb, SLAPI_TARGET_UNIQUEID, target_uuid);
+ slapi_pblock_get( pb, SLAPI_OPERATION_PARAMETERS, &op_params );
+ op_params->p.p_modrdn.modrdn_newsuperior_address.uniqueid= newsuperior_uuid; /* JCMREPL - Not very elegant */
+ }
+
+ /*
+ * The replicated modrdn operation may also contain a sequence
+ * that contains side effects of the modrdn operation, e.g. the
+ * modifiersname and modifytimestamp are updated.
+ */
+ if (NULL != modrdn_mods)
+ {
+ LDAPMod **mods;
+ Slapi_Mods smods;
+ int i;
+ slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods);
+ slapi_mods_init_passin(&smods, mods);
+ for (i = 0; NULL != modrdn_mods[i]; i++)
+ {
+ slapi_mods_add_ldapmod(&smods, modrdn_mods[i]); /* Does not copy mod */
+ }
+ mods = slapi_mods_get_ldapmods_passout(&smods);
+ slapi_pblock_set(pb, SLAPI_MODIFY_MODS, mods);
+ slapi_mods_done(&smods);
+ slapi_ch_free((void **)&modrdn_mods); /* Free container only - contents are referred to by array "mods" */
+ }
+ }
+ else
+ {
+ PR_ASSERT(0); /* JCMREPL - A Replicated Operation with no Repl Baggage control... What does that mean? */
+ }
+ }
+ else
+ {
+ /* Replicated & Fixup Operation */
+ }
+ }
+ else
+ {
+ slapi_operation_set_csngen_handler ( op, (void*)replica_generate_next_csn );
+ }
+
+ copy_operation_parameters(pb);
+
+ return 0;
+}
+
+int
+multimaster_preop_search (Slapi_PBlock *pb)
+{
+ return 0;
+}
+
+int
+multimaster_preop_compare (Slapi_PBlock *pb)
+{
+ return 0;
+}
+
+static void
+purge_entry_state_information (Slapi_PBlock *pb)
+{
+ CSN *purge_csn;
+ Object *repl_obj;
+ Replica *replica;
+
+ /* we don't want to trim RUV tombstone because we will
+ deadlock with ourself */
+ if (ruv_tombstone_op (pb))
+ return;
+
+ repl_obj = replica_get_replica_for_op(pb);
+ if (NULL != repl_obj)
+ {
+ replica = object_get_data(repl_obj);
+ if (NULL != replica)
+ {
+ purge_csn = replica_get_purge_csn(replica);
+ }
+ if (NULL != purge_csn)
+ {
+ Slapi_Entry *e;
+ int optype;
+
+ slapi_pblock_get(pb, SLAPI_OPERATION_TYPE, &optype);
+ switch (optype)
+ {
+ case SLAPI_OPERATION_MODIFY:
+ slapi_pblock_get(pb, SLAPI_MODIFY_EXISTING_ENTRY, &e);
+ break;
+ case SLAPI_OPERATION_MODRDN:
+ /* XXXggood - the following always gives a NULL entry - why? */
+ slapi_pblock_get(pb, SLAPI_MODRDN_EXISTING_ENTRY, &e);
+ break;
+ case SLAPI_OPERATION_DELETE:
+ slapi_pblock_get(pb, SLAPI_DELETE_EXISTING_ENTRY, &e);
+ break;
+ default:
+ e = NULL; /* Don't purge on ADD - not needed */
+ break;
+ }
+ if (NULL != e)
+ {
+ char csn_str[CSN_STRSIZE];
+ entry_purge_state_information(e, purge_csn);
+ /* conntion is always null */
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "Purged state information from entry %s up to "
+ "CSN %s\n", slapi_entry_get_dn(e),
+ csn_as_string(purge_csn, PR_FALSE, csn_str));
+ }
+ csn_free(&purge_csn);
+ }
+ object_release(repl_obj);
+ }
+}
+
+int
+multimaster_bepreop_add (Slapi_PBlock *pb)
+{
+ int rc= 0;
+ Slapi_Operation *op;
+ int is_replicated_operation;
+ int is_fixup_operation;
+ int is_legacy_operation;
+
+ slapi_pblock_get(pb, SLAPI_OPERATION, &op);
+ is_replicated_operation= operation_is_flag_set(op,OP_FLAG_REPLICATED);
+ is_fixup_operation= operation_is_flag_set(op,OP_FLAG_REPL_FIXUP);
+ is_legacy_operation= operation_is_flag_set(op,OP_FLAG_LEGACY_REPLICATION_DN);
+
+ /* For replicated operations, apply URP algorithm */
+ if (is_replicated_operation && !is_fixup_operation)
+ {
+ rc = urp_add_operation(pb);
+ }
+
+ return rc;
+}
+
+int
+multimaster_bepreop_delete (Slapi_PBlock *pb)
+{
+ int rc= 0;
+ Slapi_Operation *op;
+ int is_replicated_operation;
+ int is_fixup_operation;
+ int is_legacy_operation;
+
+ slapi_pblock_get(pb, SLAPI_OPERATION, &op);
+ is_replicated_operation= operation_is_flag_set(op,OP_FLAG_REPLICATED);
+ is_fixup_operation= operation_is_flag_set(op,OP_FLAG_REPL_FIXUP);
+ is_legacy_operation= operation_is_flag_set(op,OP_FLAG_LEGACY_REPLICATION_DN);
+
+ /* For replicated operations, apply URP algorithm */
+ if(is_replicated_operation && !is_fixup_operation)
+ {
+ rc = urp_delete_operation(pb);
+ }
+
+ return rc;
+}
+
+int
+multimaster_bepreop_modify (Slapi_PBlock *pb)
+{
+ int rc= 0;
+ Slapi_Operation *op;
+ int is_replicated_operation;
+ int is_fixup_operation;
+ int is_legacy_operation;
+
+ slapi_pblock_get(pb, SLAPI_OPERATION, &op);
+ is_replicated_operation= operation_is_flag_set(op,OP_FLAG_REPLICATED);
+ is_fixup_operation= operation_is_flag_set(op,OP_FLAG_REPL_FIXUP);
+ is_legacy_operation= operation_is_flag_set(op,OP_FLAG_LEGACY_REPLICATION_DN);
+
+ /* For replicated operations, apply URP algorithm */
+ if(is_replicated_operation && !is_fixup_operation)
+ {
+ rc = urp_modify_operation(pb);
+ }
+
+ /* Clean up old state information */
+ purge_entry_state_information(pb);
+
+ return rc;
+}
+
+int
+multimaster_bepreop_modrdn (Slapi_PBlock *pb)
+{
+ int rc= 0;
+ Slapi_Operation *op;
+ int is_replicated_operation;
+ int is_fixup_operation;
+ int is_legacy_operation;
+
+ slapi_pblock_get(pb, SLAPI_OPERATION, &op);
+ is_replicated_operation= operation_is_flag_set(op,OP_FLAG_REPLICATED);
+ is_fixup_operation= operation_is_flag_set(op,OP_FLAG_REPL_FIXUP);
+ is_legacy_operation= operation_is_flag_set(op,OP_FLAG_LEGACY_REPLICATION_DN);
+
+ /* For replicated operations, apply URP algorithm */
+ if(is_replicated_operation && !is_fixup_operation)
+ {
+ rc = urp_modrdn_operation(pb);
+ }
+
+ /* Clean up old state information */
+ purge_entry_state_information(pb);
+
+ return rc;
+}
+
+int
+multimaster_bepostop_modrdn (Slapi_PBlock *pb)
+{
+ return 0;
+}
+
+int
+multimaster_bepostop_delete (Slapi_PBlock *pb)
+{
+ return 0;
+}
+
+/* postop - write to changelog */
+int
+multimaster_postop_bind (Slapi_PBlock *pb)
+{
+ return 0;
+}
+
+int
+multimaster_postop_add (Slapi_PBlock *pb)
+{
+ return process_postop(pb);
+}
+
+int
+multimaster_postop_delete (Slapi_PBlock *pb)
+{
+ int rc;
+ Slapi_Operation *op;
+
+ slapi_pblock_get(pb, SLAPI_OPERATION, &op);
+ if ( ! operation_is_flag_set (op, OP_FLAG_REPL_FIXUP) )
+ {
+ urp_post_delete_operation (pb);
+ }
+ rc = process_postop(pb);
+ return rc;
+}
+
+int
+multimaster_postop_modify (Slapi_PBlock *pb)
+{
+ return process_postop(pb);
+}
+
+int
+multimaster_postop_modrdn (Slapi_PBlock *pb)
+{
+ int rc;
+ Slapi_Operation *op;
+
+ slapi_pblock_get(pb, SLAPI_OPERATION, &op);
+ if ( ! operation_is_flag_set (op, OP_FLAG_REPL_FIXUP) )
+ {
+ urp_post_modrdn_operation (pb);
+ }
+ rc = process_postop(pb);
+ return rc;
+}
+
+
+/* Helper functions */
+
+/*
+ * This function makes a copy of the operation parameters
+ * and stashes them in the consumer operation extension.
+ * This is where the 5.0 Change Log will get the operation
+ * details from.
+ */
+static void
+copy_operation_parameters(Slapi_PBlock *pb)
+{
+ Slapi_Operation *op = NULL;
+ struct slapi_operation_parameters *op_params;
+ supplier_operation_extension *opext;
+ Object *repl_obj;
+ Replica *replica;
+
+ repl_obj = replica_get_replica_for_op (pb);
+
+ /* we are only interested in the updates to replicas */
+ if (repl_obj)
+ {
+ /* we only save the original operation parameters for replicated operations
+ since client operations don't go through urp engine and pblock data can be logged */
+ slapi_pblock_get( pb, SLAPI_OPERATION, &op );
+ PR_ASSERT (op);
+
+ replica = (Replica*)object_get_data (repl_obj);
+ PR_ASSERT (replica);
+
+ opext = (supplier_operation_extension*) repl_sup_get_ext (REPL_SUP_EXT_OP, op);
+ if (operation_is_flag_set(op,OP_FLAG_REPLICATED) &&
+ !operation_is_flag_set(op, OP_FLAG_REPL_FIXUP))
+ {
+ slapi_pblock_get (pb, SLAPI_OPERATION_PARAMETERS, &op_params);
+ opext->operation_parameters= operation_parameters_dup(op_params);
+ }
+
+ /* this condition is needed to avoid re-entering lock when
+ ruv state is updated */
+ if (!operation_is_flag_set(op, OP_FLAG_REPL_FIXUP))
+ {
+ /* save replica generation in case it changes */
+ opext->repl_gen = replica_get_generation (replica);
+ }
+
+ object_release (repl_obj);
+ }
+}
+
+/*
+ * Helper function: update the RUV so that it reflects the
+ * locally-processed update. This is called for both replicated
+ * and non-replicated operations.
+ */
+static void
+update_ruv_component(Replica *replica, CSN *opcsn, Slapi_PBlock *pb)
+{
+ PRBool legacy;
+ char *purl;
+
+ if (!replica || !opcsn)
+ return;
+
+ /* Replica configured, so update its ruv */
+ legacy = replica_is_legacy_consumer (replica);
+ if (legacy)
+ purl = replica_get_legacy_purl (replica);
+ else
+ purl = (char*)replica_get_purl_for_op (replica, pb, opcsn);
+
+ replica_update_ruv(replica, opcsn, purl);
+
+ if (legacy)
+ {
+ slapi_ch_free ((void**)&purl);
+ }
+}
+
+/*
+ * Write the changelog. Note: it is an error to call write_changelog_and_ruv() for fixup
+ * operations. The caller should avoid calling this function if the operation is
+ * a fixup op.
+ * Also update the ruv - we need to do this while we have the replica lock acquired
+ * so that the csn is written to the changelog and the ruv is updated with the csn
+ * in one atomic operation - if there is no changelog, we still need to update
+ * the ruv (e.g. for consumers)
+ */
+static int
+write_changelog_and_ruv (Slapi_PBlock *pb)
+{
+ int rc;
+ slapi_operation_parameters *op_params = NULL;
+ Object *repl_obj;
+ int return_value = 0;
+ Replica *r;
+
+ /* we only log changes for operations applied to a replica */
+ repl_obj = replica_get_replica_for_op (pb);
+ if (repl_obj == NULL)
+ return 0;
+
+ r = (Replica*)object_get_data (repl_obj);
+ PR_ASSERT (r);
+
+ if (replica_is_flag_set (r, REPLICA_LOG_CHANGES) &&
+ (cl5GetState () == CL5_STATE_OPEN))
+ {
+ supplier_operation_extension *opext = NULL;
+ const char *repl_name;
+ char *repl_gen;
+ Slapi_Operation *op;
+
+ slapi_pblock_get(pb, SLAPI_OPERATION, &op);
+ opext = (supplier_operation_extension*) repl_sup_get_ext (REPL_SUP_EXT_OP, op);
+ PR_ASSERT (opext);
+
+ /* get replica generation and replica name to pass to the write function */
+ repl_name = replica_get_name (r);
+ repl_gen = opext->repl_gen;
+ PR_ASSERT (repl_name && repl_gen);
+
+ /* for replicated operations, we log the original, non-urp data which is
+ saved in the operation extension */
+ if (operation_is_flag_set(op,OP_FLAG_REPLICATED))
+ {
+ PR_ASSERT (opext->operation_parameters);
+ op_params = opext->operation_parameters;
+ }
+ else /* since client operations don't go through urp, we log the operation data in pblock */
+ {
+ Slapi_Entry *e = NULL;
+ const char *uniqueid = NULL;
+
+ slapi_pblock_get (pb, SLAPI_OPERATION_PARAMETERS, &op_params);
+ PR_ASSERT (op_params);
+
+ /* need to set uniqueid operation parameter */
+ /* we try to use the appropriate entry (pre op or post op)
+ depending on the type of operation (add, delete, modify)
+ However, in some cases, the backend operation may fail in
+ a non fatal way (e.g. attempt to remove an attribute value
+ that does not exist) but we still need to log the change.
+ So, the POST_OP entry may not have been set in the FE modify
+ code. In that case, we use the PRE_OP entry.
+ */
+ slapi_pblock_get (pb, SLAPI_ENTRY_POST_OP, &e);
+ if ((e == NULL) ||
+ (op_params->operation_type == SLAPI_OPERATION_DELETE))
+ {
+ slapi_pblock_get (pb, SLAPI_ENTRY_PRE_OP, &e);
+ }
+
+ PR_ASSERT (e);
+
+ uniqueid = slapi_entry_get_uniqueid (e);
+ PR_ASSERT (uniqueid);
+
+ op_params->target_address.uniqueid = slapi_ch_strdup (uniqueid);
+ }
+
+ /* we might have stripped all the mods - in that case we do not
+ log the operation */
+ if (op_params->operation_type != SLAPI_OPERATION_MODIFY ||
+ op_params->p.p_modify.modify_mods != NULL)
+ {
+ if (cl5_is_diskfull() && !cl5_diskspace_is_available())
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "write_changelog_and_ruv: Skipped due to DISKFULL\n");
+ return 0;
+ }
+ rc = cl5WriteOperation(repl_name, repl_gen, op_params,
+ !operation_is_flag_set(op, OP_FLAG_REPLICATED));
+ if (rc != CL5_SUCCESS)
+ {
+ char csn_str[CSN_STRSIZE];
+ /* ONREPL - log error */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "write_changelog_and_ruv: can't add a change for "
+ "%s (uniqid: %s, optype: %u) to changelog csn %s\n",
+ op_params->target_address.dn,
+ op_params->target_address.uniqueid,
+ op_params->operation_type,
+ csn_as_string(op_params->csn, PR_FALSE, csn_str));
+ return_value = 1;
+ }
+ }
+
+ if (!operation_is_flag_set(op,OP_FLAG_REPLICATED))
+ {
+ slapi_ch_free((void**)&op_params->target_address.uniqueid);
+ }
+ }
+
+ /*
+ This was moved because we need to write the changelog and update
+ the ruv in one atomic operation - I have seen cases where the inc
+ protocol thread interrupts this thread between the time the changelog
+ is written and the ruv is updated - this causes confusion in several
+ places, especially in _cl5SkipReplayEntry since it cannot find the csn it
+ just read from the changelog in either the supplier or consumer ruv
+ */
+ if (0 == return_value) {
+ Slapi_Operation *op;
+ CSN *opcsn;
+
+ slapi_pblock_get( pb, SLAPI_OPERATION, &op );
+ opcsn = operation_get_csn(op);
+ update_ruv_component(r, opcsn, pb);
+ }
+
+ object_release (repl_obj);
+ return return_value;
+}
+
+/*
+ * Postop processing - write the changelog if the operation resulted in
+ * an LDAP_SUCCESS result code, update the RUV, and notify the replication
+ * agreements about the change.
+ * If the result code is not LDAP_SUCCESS, then cancel the operation CSN.
+ */
+static int
+process_postop (Slapi_PBlock *pb)
+{
+ int rc = LDAP_SUCCESS;
+ Slapi_Operation *op;
+ Slapi_Backend *be;
+ int is_replicated_operation = 0;
+ CSN *opcsn = NULL;
+ char sessionid[REPL_SESSION_ID_SIZE];
+
+ /* we just let fixup operations through */
+ slapi_pblock_get( pb, SLAPI_OPERATION, &op );
+ if ((operation_is_flag_set(op, OP_FLAG_REPL_FIXUP)) ||
+ (operation_is_flag_set(op, OP_FLAG_TOMBSTONE_ENTRY)))
+ {
+ return 0;
+ }
+
+ /* ignore operations intended for chaining backends - they will be
+ replicated back to us or should be ignored anyway
+ replicated operations should be processed normally, as they should
+ be going to a local backend */
+ is_replicated_operation= operation_is_flag_set(op,OP_FLAG_REPLICATED);
+ slapi_pblock_get(pb, SLAPI_BACKEND, &be);
+ if (!is_replicated_operation &&
+ slapi_be_is_flag_set(be,SLAPI_BE_FLAG_REMOTE_DATA))
+ {
+ return 0;
+ }
+
+ get_repl_session_id (pb, sessionid, &opcsn);
+
+ slapi_pblock_get(pb, SLAPI_RESULT_CODE, &rc);
+ /*
+ * Don't abandon writing changelog since we'd do everything
+ * possible to keep the changelog in sync with the backend
+ * db which was committed before this function was called.
+ *
+ * if (rc == LDAP_SUCCESS && !slapi_op_abandoned(pb))
+ */
+ if (rc == LDAP_SUCCESS)
+ {
+ rc = write_changelog_and_ruv(pb);
+ if (rc == 0)
+ {
+ agmtlist_notify_all(pb);
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "%s process postop: error writing changelog and ruv\n", sessionid);
+ }
+ }
+ else if (opcsn)
+ {
+ rc = cancel_opcsn (pb);
+
+ /* Don't try to get session id since conn is always null */
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s process postop: canceling operation csn\n", sessionid);
+ }
+
+ /* the target unique id is set in the modify_preop above, so
+ we need to free it */
+ /* The following bunch of frees code does not belong to this place
+ * but rather to operation_free who should be responsible for calling
+ * operation_parameters_free and it doesn't. I guess this is like this
+ * because several crashes happened in the past regarding some opparams
+ * that were getting individually freed before they should. Whatever
+ * the case, this is not the place and we should make sure that this
+ * code gets removed for 5.next and substituted by the strategy (operation_free).
+ * For 5.0, such change is too risky, so this will be done here */
+ if (is_replicated_operation)
+ {
+ slapi_operation_parameters *op_params = NULL;
+ int optype = 0;
+ /* target uid and csn are set for all repl operations. Free them */
+ char *target_uuid = NULL;
+ slapi_pblock_get(pb, SLAPI_OPERATION_TYPE, &optype);
+ slapi_pblock_get(pb, SLAPI_TARGET_UNIQUEID, &target_uuid);
+ slapi_pblock_set(pb, SLAPI_TARGET_UNIQUEID, NULL);
+ slapi_ch_free((void**)&target_uuid);
+ if (optype == SLAPI_OPERATION_ADD) {
+ slapi_pblock_get( pb, SLAPI_OPERATION_PARAMETERS, &op_params );
+ slapi_ch_free((void **) &op_params->p.p_add.parentuniqueid);
+ }
+ if (optype == SLAPI_OPERATION_MODRDN) {
+ slapi_pblock_get( pb, SLAPI_OPERATION_PARAMETERS, &op_params );
+ slapi_ch_free((void **) &op_params->p.p_modrdn.modrdn_newsuperior_address.uniqueid);
+ }
+ }
+ if (NULL == opcsn)
+ opcsn = operation_get_csn(op);
+ if (opcsn)
+ csn_free(&opcsn);
+
+ return rc;
+}
+
+
+/*
+ * Cancel an operation CSN. This removes it from any CSN pending lists.
+ * This function is called when a previously-generated CSN will not
+ * be needed, e.g. if the update operation produced an error.
+ */
+static int
+cancel_opcsn (Slapi_PBlock *pb)
+{
+ Object *repl_obj;
+ Slapi_Operation *op = NULL;
+
+ PR_ASSERT (pb);
+
+ repl_obj = replica_get_replica_for_op (pb);
+ slapi_pblock_get(pb, SLAPI_OPERATION, &op);
+ PR_ASSERT (op);
+ if (repl_obj)
+ {
+ Replica *r;
+ Object *gen_obj;
+ CSNGen *gen;
+ CSN *opcsn;
+
+ r = (Replica*)object_get_data (repl_obj);
+ PR_ASSERT (r);
+ opcsn = operation_get_csn(op);
+
+ if (!operation_is_flag_set(op,OP_FLAG_REPLICATED))
+ {
+ /* get csn generator for the replica */
+ gen_obj = replica_get_csngen (r);
+ PR_ASSERT (gen_obj);
+ gen = (CSNGen*)object_get_data (gen_obj);
+
+ if (NULL != opcsn)
+ {
+ csngen_abort_csn (gen, operation_get_csn(op));
+ }
+
+ object_release (gen_obj);
+ }
+ else if (!operation_is_flag_set(op,OP_FLAG_REPL_FIXUP))
+ {
+ Object *ruv_obj;
+
+ ruv_obj = replica_get_ruv (r);
+ PR_ASSERT (ruv_obj);
+ ruv_cancel_csn_inprogress ((RUV*)object_get_data (ruv_obj), opcsn);
+ object_release (ruv_obj);
+ }
+
+ object_release (repl_obj);
+ }
+
+ return 0;
+}
+
+
+
+/*
+ * Return non-zero if the target entry DN is the DN of the RUV tombstone
+ * entry.
+ * The entry has rdn of nsuniqueid = ffffffff-ffffffff-ffffffff-ffffffff
+ */
+static int
+ruv_tombstone_op (Slapi_PBlock *pb)
+{
+ char *uniqueid;
+ int rc;
+
+ slapi_pblock_get (pb, SLAPI_TARGET_UNIQUEID, &uniqueid);
+
+ rc = uniqueid && strcasecmp (uniqueid, RUV_STORAGE_ENTRY_UNIQUEID) == 0;
+
+ return rc;
+}
+
+/* we don't want to process replicated operations with csn smaller
+ than the corresponding csn in the consumer's ruv */
+static PRBool
+process_operation (Slapi_PBlock *pb, const CSN *csn)
+{
+ Object *r_obj;
+ Replica *r;
+ Object *ruv_obj;
+ RUV *ruv;
+ int rc;
+
+ r_obj = replica_get_replica_for_op(pb);
+ if (r_obj == NULL)
+ {
+ char sessionid[REPL_SESSION_ID_SIZE];
+ get_repl_session_id (pb, sessionid, NULL);
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "%s process_operation: "
+ "can't locate replica for the replicated operation\n",
+ sessionid );
+ return PR_FALSE;
+ }
+
+ r = (Replica*)object_get_data (r_obj);
+ PR_ASSERT (r);
+
+ ruv_obj = replica_get_ruv (r);
+ PR_ASSERT (ruv_obj);
+
+ ruv = (RUV*)object_get_data (ruv_obj);
+ PR_ASSERT (ruv);
+
+ rc = ruv_add_csn_inprogress (ruv, csn);
+
+ object_release (ruv_obj);
+ object_release (r_obj);
+
+ return (rc == RUV_SUCCESS);
+}
+
+static PRBool
+is_mmr_replica (Slapi_PBlock *pb)
+{
+ Object *r_obj;
+ Replica *r;
+ PRBool mmr;
+
+ r_obj = replica_get_replica_for_op(pb);
+ if (r_obj == NULL)
+ {
+ return PR_FALSE;
+ }
+
+ r = (Replica*)object_get_data (r_obj);
+ PR_ASSERT (r);
+
+ mmr = !replica_is_legacy_consumer (r);
+
+ object_release (r_obj);
+
+ return mmr;
+}
+
+static const char *replica_get_purl_for_op (const Replica *r, Slapi_PBlock *pb, const CSN *opcsn)
+{
+ int is_replicated_op;
+ const char *purl;
+
+ slapi_pblock_get(pb, SLAPI_IS_MMR_REPLICATED_OPERATION, &is_replicated_op);
+
+ if (!is_replicated_op)
+ {
+ purl = multimaster_get_local_purl();
+ }
+ else
+ {
+ /* Get the appropriate partial URL from the supplier RUV */
+ Slapi_Connection *conn;
+ consumer_connection_extension *connext;
+ slapi_pblock_get(pb, SLAPI_CONNECTION, &conn);
+ connext = (consumer_connection_extension *)repl_con_get_ext(
+ REPL_CON_EXT_CONN, conn);
+ if (NULL == connext || NULL == connext->supplier_ruv)
+ {
+ char sessionid[REPL_SESSION_ID_SIZE];
+ get_repl_session_id (pb, sessionid, NULL);
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "%s replica_get_purl_for_op: "
+ "cannot obtain consumer connection extension or supplier_ruv.\n",
+ sessionid);
+ }
+ else
+ {
+ purl = ruv_get_purl_for_replica(connext->supplier_ruv,
+ csn_get_replicaid(opcsn));
+ }
+ }
+
+ return purl;
+}
+
+/* ONREPL at the moment, I decided not to trim copiedFrom and copyingFrom
+ attributes when sending operation to replicas. This is because, each
+ operation results in a state information stored in the database and
+ if we don't replay all operations we will endup with state inconsistency.
+
+ Keeping the function just in case
+ */
+static void strip_legacy_info (slapi_operation_parameters *op_params)
+{
+ switch (op_params->operation_type)
+ {
+ case SLAPI_OPERATION_ADD:
+ slapi_entry_delete_values_sv(op_params->p.p_add.target_entry,
+ type_copiedFrom, NULL);
+ slapi_entry_delete_values_sv(op_params->p.p_add.target_entry,
+ type_copyingFrom, NULL);
+ break;
+ case SLAPI_OPERATION_MODIFY:
+ {
+ Slapi_Mods smods;
+ LDAPMod *mod;
+
+ slapi_mods_init_byref(&smods, op_params->p.p_modify.modify_mods);
+ mod = slapi_mods_get_first_mod(&smods);
+ while (mod)
+ {
+ /* modify just to update copiedFrom or copyingFrom attribute
+ does not contain modifiersname or modifytime - so we don't
+ have to strip them */
+ if (strcasecmp (mod->mod_type, type_copiedFrom) == 0 ||
+ strcasecmp (mod->mod_type, type_copyingFrom) == 0)
+ slapi_mods_remove(&smods);
+ mod = slapi_mods_get_next_mod(&smods);
+ }
+
+ op_params->p.p_modify.modify_mods = slapi_mods_get_ldapmods_passout (&smods);
+ slapi_mods_done (&smods);
+
+ break;
+ }
+
+ default: break;
+ }
+}
+
+/* this function is called when state of a backend changes */
+void
+multimaster_be_state_change (void *handle, char *be_name, int old_be_state, int new_be_state)
+{
+ Object *r_obj;
+ Replica *r;
+
+ /* check if we have replica associated with the backend */
+ r_obj = replica_get_for_backend (be_name);
+ if (r_obj == NULL)
+ return;
+
+ r = (Replica*)object_get_data (r_obj);
+ PR_ASSERT (r);
+
+ if (new_be_state == SLAPI_BE_STATE_ON)
+ {
+ /* backend came back online - restart replication */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "multimaster_be_state_change: "
+ "replica %s is coming online; enabling replication\n",
+ slapi_sdn_get_ndn (replica_get_root (r)));
+ replica_enable_replication (r);
+ }
+ else if (new_be_state == SLAPI_BE_STATE_OFFLINE)
+ {
+ /* backend is about to be taken down - disable replication */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "multimaster_be_state_change: "
+ "replica %s is going offline; disabling replication\n",
+ slapi_sdn_get_ndn (replica_get_root (r)));
+ replica_disable_replication (r, r_obj);
+ }
+ else if (new_be_state == SLAPI_BE_STATE_DELETE)
+ {
+ /* backend is about to be removed - disable replication */
+ if (old_be_state == SLAPI_BE_STATE_ON)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "multimaster_be_state_change: "
+ "replica %s is about to be deleted; disabling replication\n",
+ slapi_sdn_get_ndn (replica_get_root (r)));
+ replica_disable_replication (r, r_obj);
+ }
+ }
+
+ object_release (r_obj);
+}
+
+static void
+close_changelog_for_replica (Object *r_obj)
+{
+ if (cl5GetState () == CL5_STATE_OPEN)
+ cl5CloseDB (r_obj);
+}
diff --git a/ldap/servers/plugins/replication/repl5_prot_private.h b/ldap/servers/plugins/replication/repl5_prot_private.h
new file mode 100644
index 00000000..fbaf96e4
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl5_prot_private.h
@@ -0,0 +1,66 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#ifndef _REPL5_PROT_PRIVATE_H_
+#define _REPL5_PROT_PRIVATE_H_
+
+#define ACQUIRE_SUCCESS 101
+#define ACQUIRE_REPLICA_BUSY 102
+#define ACQUIRE_FATAL_ERROR 103
+#define ACQUIRE_CONSUMER_WAS_UPTODATE 104
+#define ACQUIRE_TRANSIENT_ERROR 105
+
+typedef struct private_repl_protocol
+{
+ void (*delete)(struct private_repl_protocol **);
+ void (*run)(struct private_repl_protocol *);
+ int (*stop)(struct private_repl_protocol *);
+ int (*status)(struct private_repl_protocol *);
+ void (*notify_update)(struct private_repl_protocol *);
+ void (*notify_agmt_changed)(struct private_repl_protocol *);
+ void (*notify_window_opened)(struct private_repl_protocol *);
+ void (*notify_window_closed)(struct private_repl_protocol *);
+ void (*update_now)(struct private_repl_protocol *);
+ PRLock *lock;
+ PRCondVar *cvar;
+ int stopped;
+ int terminate;
+ PRUint32 eventbits;
+ Repl_Connection *conn;
+ int last_acquire_response_code;
+ Repl_Agmt *agmt;
+ Object *replica_object;
+ void *private;
+ PRBool replica_acquired;
+} Private_Repl_Protocol;
+
+extern Private_Repl_Protocol *Repl_5_Inc_Protocol_new();
+extern Private_Repl_Protocol *Repl_5_Tot_Protocol_new();
+
+#define PROTOCOL_TERMINATION_NORMAL 301
+#define PROTOCOL_TERMINATION_ABNORMAL 302
+#define PROTOCOL_TERMINATION_NEEDS_TOTAL_UPDATE 303
+
+#define RESUME_DO_TOTAL_UPDATE 401
+#define RESUME_DO_INCREMENTAL_UPDATE 402
+#define RESUME_TERMINATE 403
+#define RESUME_SUSPEND 404
+
+/* Backoff timer settings for reconnect */
+#define PROTOCOL_BACKOFF_MINIMUM 3 /* 3 seconds */
+#define PROTOCOL_BACKOFF_MAXIMUM (60 * 5) /* 5 minutes */
+/* Backoff timer settings for replica busy reconnect */
+#define PROTOCOL_BUSY_BACKOFF_MINIMUM PROTOCOL_BACKOFF_MINIMUM
+#define PROTOCOL_BUSY_BACKOFF_MAXIMUM PROTOCOL_BUSY_BACKOFF_MINIMUM
+
+/* protocol related functions */
+void release_replica(Private_Repl_Protocol *prp);
+int acquire_replica(Private_Repl_Protocol *prp, char *prot_oid, RUV **ruv);
+BerElement *entry2bere(const Slapi_Entry *e);
+CSN *get_current_csn(Slapi_DN *replarea_sdn);
+char* protocol_response2string (int response);
+
+#endif /* _REPL5_PROT_PRIVATE_H_ */
diff --git a/ldap/servers/plugins/replication/repl5_protocol.c b/ldap/servers/plugins/replication/repl5_protocol.c
new file mode 100644
index 00000000..725bf3f2
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl5_protocol.c
@@ -0,0 +1,502 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+/* repl5_protocol.c */
+/*
+
+ The replication protocol object manages the replication protocol for
+ a given replica. It determines which protocol(s) are appropriate to
+ use when updating a given replica. It also knows how to arbitrate
+ incremental and total update protocols for a given replica.
+
+*/
+
+#include "repl5.h"
+#include "repl5_prot_private.h"
+
+#define PROTOCOL_5_INCREMENTAL 1
+#define PROTOCOL_5_TOTAL 2
+#define PROTOCOL_4_INCREMENTAL 3
+#define PROTOCOL_4_TOTAL 4
+
+typedef struct repl_protocol
+{
+ Private_Repl_Protocol *prp_incremental; /* inc protocol to use */
+ Private_Repl_Protocol *prp_total; /* total protocol to use */
+ Private_Repl_Protocol *prp_active_protocol; /* Pointer to active protocol */
+ Repl_Agmt *agmt; /* The replication agreement we're servicing */
+ Repl_Connection *conn; /* Connection to remote server */
+ Object *replica_object; /* Local replica. If non-NULL, replica object is acquired */
+ int state;
+ int next_state;
+ PRLock *lock;
+} repl_protocol;
+
+
+/* States */
+#define STATE_FINISHED 503
+#define STATE_BAD_STATE_SHOULD_NEVER_HAPPEN 599
+
+/* Forward declarations */
+static Private_Repl_Protocol *private_protocol_factory(Repl_Protocol *rp, int type);
+
+
+
+
+/*
+ * Create a new protocol instance.
+ */
+Repl_Protocol *
+prot_new(Repl_Agmt *agmt, int protocol_state)
+{
+ Slapi_DN *replarea_sdn = NULL;
+ Repl_Protocol *rp = (Repl_Protocol *)slapi_ch_malloc(sizeof(Repl_Protocol));
+
+ rp->prp_incremental = rp->prp_total = rp->prp_active_protocol = NULL;
+ if (protocol_state == STATE_PERFORMING_TOTAL_UPDATE)
+ {
+ rp->state = STATE_PERFORMING_TOTAL_UPDATE;
+ }
+ else
+ {
+ rp->state = STATE_PERFORMING_INCREMENTAL_UPDATE;
+ }
+ rp->next_state = STATE_PERFORMING_INCREMENTAL_UPDATE;
+ if ((rp->lock = PR_NewLock()) == NULL)
+ {
+ goto loser;
+ }
+ rp->agmt = agmt;
+ if ((rp->conn = conn_new(agmt)) == NULL)
+ {
+ goto loser;
+ }
+ /* Acquire the local replica object */
+ replarea_sdn = agmt_get_replarea(agmt);
+ rp->replica_object = replica_get_replica_from_dn(replarea_sdn);
+ if (NULL == rp->replica_object)
+ {
+ /* Whoa, no local replica!?!? */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Unable to locate replica object for local replica %s\n",
+ agmt_get_long_name(agmt),
+ slapi_sdn_get_dn(replarea_sdn));
+ goto loser;
+ }
+ rp->prp_incremental = private_protocol_factory(rp, PROTOCOL_5_INCREMENTAL);
+ rp->prp_total = private_protocol_factory(rp, PROTOCOL_5_TOTAL);
+ /* XXXggood register callback handlers for entries updated, and
+ schedule window enter/leave. */
+ slapi_sdn_free(&replarea_sdn);
+
+ return rp;
+loser:
+ prot_delete(&rp);
+ return NULL;
+}
+
+
+
+
+
+Object *
+prot_get_replica_object(Repl_Protocol *rp)
+{
+ PR_ASSERT(NULL != rp);
+ return rp->replica_object;
+}
+
+
+
+
+
+Repl_Agmt *
+prot_get_agreement(Repl_Protocol *rp)
+{
+ /* MAB: rp might be NULL for disabled suffixes. Don't ASSERT on it */
+ if (NULL == rp) return NULL;
+ return rp->agmt;
+}
+
+
+
+
+void
+prot_free(Repl_Protocol **rpp)
+{
+ Repl_Protocol *rp = NULL;
+
+ if (rpp == NULL || *rpp == NULL) return;
+
+ rp = *rpp;
+
+ PR_Lock(rp->lock);
+ if (NULL != rp->prp_incremental)
+ {
+ rp->prp_incremental->delete(&rp->prp_incremental);
+ }
+ if (NULL != rp->prp_total)
+ {
+ rp->prp_total->delete(&rp->prp_total);
+ }
+ if (NULL != rp->replica_object)
+ {
+ object_release(rp->replica_object);
+ }
+ if (NULL != rp->conn)
+ {
+ conn_delete(rp->conn);
+ }
+ rp->prp_active_protocol = NULL;
+ PR_Unlock(rp->lock);
+ slapi_ch_free((void **)rpp);
+}
+
+/*
+ * Destroy a protocol instance XXXggood not complete
+ */
+void
+prot_delete(Repl_Protocol **rpp)
+{
+ Repl_Protocol *rp;
+
+ PR_ASSERT(NULL != rpp);
+ rp = *rpp;
+ /* MAB: rp might be NULL for disabled suffixes. Don't ASSERT on it */
+ if (NULL != rp)
+ {
+ prot_stop(rp);
+ prot_free(rpp);
+ }
+}
+
+
+
+
+
+/*
+ * Get the connection object.
+ */
+Repl_Connection *
+prot_get_connection(Repl_Protocol *rp)
+{
+ Repl_Connection *return_value;
+
+ PR_ASSERT(NULL != rp);
+ PR_Lock(rp->lock);
+ return_value = rp->conn;
+ PR_Unlock(rp->lock);
+ return return_value;
+}
+
+
+
+
+/*
+ * This function causes the total protocol to start.
+ * This is accomplished by registering a state transition
+ * to a new state, and then signaling the incremental
+ * protocol to stop.
+ */
+void
+prot_initialize_replica(Repl_Protocol *rp)
+{
+ PR_ASSERT(NULL != rp);
+
+ PR_Lock(rp->lock);
+ /* check that total protocol is not running */
+ rp->next_state = STATE_PERFORMING_TOTAL_UPDATE;
+ /* Stop the incremental protocol, if running */
+ rp->prp_incremental->stop(rp->prp_incremental);
+ if (rp->prp_total) agmt_set_last_init_status(rp->prp_total->agmt, 0, 0, NULL);
+ PR_Unlock(rp->lock);
+}
+
+
+
+
+
+/*
+ * Main thread for protocol manager.
+
+This is a simple state machine. State transition table:
+
+Initial state: incremental update
+
+STATE EVENT NEXT STATE
+----- ----- ----------
+incremental update shutdown finished
+incremental update total update requested total update
+total update shutdown finished
+total update update complete incremental update
+finished (any) finished
+
+*/
+
+static void
+prot_thread_main(void *arg)
+{
+ Repl_Protocol *rp = (Repl_Protocol *)arg;
+ int done;
+
+ PR_ASSERT(NULL != rp);
+
+ if (rp->agmt) {
+ set_thread_private_agmtname (agmt_get_long_name(rp->agmt));
+ }
+
+ done = 0;
+
+ while (!done)
+ {
+ switch (rp->state)
+ {
+ case STATE_PERFORMING_INCREMENTAL_UPDATE:
+ /* Run the incremental update protocol */
+ PR_Lock(rp->lock);
+ dev_debug("prot_thread_main(STATE_PERFORMING_INCREMENTAL_UPDATE): begin");
+ rp->prp_active_protocol = rp->prp_incremental;
+ PR_Unlock(rp->lock);
+ rp->prp_incremental->run(rp->prp_incremental);
+ dev_debug("prot_thread_main(STATE_PERFORMING_INCREMENTAL_UPDATE): end");
+ break;
+ case STATE_PERFORMING_TOTAL_UPDATE:
+ PR_Lock(rp->lock);
+
+ /* stop incremental protocol if running */
+ rp->prp_active_protocol = rp->prp_total;
+ /* After total protocol finished, return to incremental */
+ rp->next_state = STATE_PERFORMING_INCREMENTAL_UPDATE;
+ PR_Unlock(rp->lock);
+ /* Run the total update protocol */
+ dev_debug("prot_thread_main(STATE_PERFORMING_TOTAL_UPDATE): begin");
+ rp->prp_total->run(rp->prp_total);
+ dev_debug("prot_thread_main(STATE_PERFORMING_TOTAL_UPDATE): end");
+ /* update the agreement entry to notify clients that
+ replica initialization is completed. */
+ agmt_replica_init_done (rp->agmt);
+
+ break;
+ case STATE_FINISHED:
+ dev_debug("prot_thread_main(STATE_FINISHED): exiting prot_thread_main");
+ done = 1;
+ break;
+ }
+ rp->state = rp->next_state;
+ }
+}
+
+
+/*
+ * Start a thread to handle the replication protocol.
+ */
+void
+prot_start(Repl_Protocol *rp)
+{
+ PR_ASSERT(NULL != rp);
+ if (NULL != rp)
+ {
+ if (PR_CreateThread(PR_USER_THREAD, prot_thread_main, (void *)rp,
+ PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_UNJOINABLE_THREAD, SLAPD_DEFAULT_THREAD_STACKSIZE) == NULL)
+ {
+ PRErrorCode prerr = PR_GetError();
+
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Unable to create protocol thread; NSPR error - %d, %s\n",
+ agmt_get_long_name(rp->agmt),
+ prerr, slapd_pr_strerror(prerr));
+ }
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Unable to start "
+ "protocol object - NULL protocol object passed to prot_start.\n");
+ }
+}
+
+
+
+
+
+/*
+ * Stop a protocol instance.
+ */
+void
+prot_stop(Repl_Protocol *rp)
+{
+ PR_ASSERT(NULL != rp);
+ if (NULL != rp)
+ {
+ PR_Lock(rp->lock);
+ rp->next_state = STATE_FINISHED;
+ if (NULL != rp->prp_incremental)
+ {
+ if (rp->prp_incremental->stop(rp->prp_incremental) != 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "Warning: incremental protocol for replica \"%s\" "
+ "did not shut down properly.\n",
+ agmt_get_long_name(rp->agmt));
+ }
+ }
+ if (NULL != rp->prp_total)
+ {
+ if (rp->prp_total->stop(rp->prp_total) != 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "Warning: total protocol for replica \"%s\" "
+ "did not shut down properly.\n",
+ agmt_get_long_name(rp->agmt));
+ }
+ }
+ PR_Unlock(rp->lock);
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Error: prot_stop() "
+ " called on NULL protocol instance.\n");
+ }
+}
+
+
+
+
+
+/*
+ * Call the notify_update method of the incremental or total update
+ * protocol, is either is active.
+ */
+void
+prot_notify_update(Repl_Protocol *rp)
+{
+ /* MAB: rp might be NULL for disabled suffixes. Don't ASSERT on it */
+ if (NULL == rp) return;
+
+ PR_Lock(rp->lock);
+ if (NULL != rp->prp_active_protocol)
+ {
+ rp->prp_active_protocol->notify_update(rp->prp_active_protocol);
+ }
+ PR_Unlock(rp->lock);
+}
+
+
+/*
+ * Call the notify_agmt_changed method of the incremental or total update
+ * protocol, is either is active.
+ */
+void
+prot_notify_agmt_changed(Repl_Protocol *rp, char * agmt_name)
+{
+ /* MAB: rp might be NULL for disabled suffixes. Don't ASSERT on it */
+ if (NULL == rp) {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "Replication agreement for %s could not be updated. "
+ "For replication to take place, please enable the suffix "
+ "and restart the server\n", agmt_name);
+ return;
+ }
+
+ PR_Lock(rp->lock);
+ if (NULL != rp->prp_active_protocol)
+ {
+ rp->prp_active_protocol->notify_agmt_changed(rp->prp_active_protocol);
+ }
+ PR_Unlock(rp->lock);
+}
+
+
+void
+prot_notify_window_opened (Repl_Protocol *rp)
+{
+ /* MAB: rp might be NULL for disabled suffixes. Don't ASSERT on it */
+ if (NULL == rp) return;
+
+ PR_Lock(rp->lock);
+ if (NULL != rp->prp_active_protocol)
+ {
+ rp->prp_active_protocol->notify_window_opened(rp->prp_active_protocol);
+ }
+ PR_Unlock(rp->lock);
+}
+
+
+void
+prot_notify_window_closed (Repl_Protocol *rp)
+{
+ /* MAB: rp might be NULL for disabled suffixes. Don't ASSERT on it */
+ if (NULL == rp) return;
+
+ PR_Lock(rp->lock);
+ if (NULL != rp->prp_active_protocol)
+ {
+ rp->prp_active_protocol->notify_window_closed(rp->prp_active_protocol);
+ }
+ PR_Unlock(rp->lock);
+}
+
+
+int
+prot_status(Repl_Protocol *rp)
+{
+ int return_status = PROTOCOL_STATUS_UNKNOWN;
+
+ /* MAB: rp might be NULL for disabled suffixes. Don't ASSERT on it */
+ if (NULL != rp)
+ {
+ PR_Lock(rp->lock);
+ if (NULL != rp->prp_active_protocol)
+ {
+ return_status = rp->prp_active_protocol->status(rp->prp_active_protocol);
+ }
+ PR_Unlock(rp->lock);
+ }
+ return return_status;
+}
+
+
+/*
+ * Start an incremental protocol session, even if we're not
+ * currently in a schedule window.
+ * If the total protocol is active, do nothing.
+ * Otherwise, notify the incremental protocol that it should
+ * run once.
+ */
+void
+prot_replicate_now(Repl_Protocol *rp)
+{
+ /* MAB: rp might be NULL for disabled suffixes. Don't ASSERT on it */
+
+ if (NULL != rp)
+ {
+ PR_Lock(rp->lock);
+ if (rp->prp_incremental == rp->prp_active_protocol)
+ {
+ rp->prp_active_protocol->update_now(rp->prp_active_protocol);
+ }
+ PR_Unlock(rp->lock);
+ }
+}
+
+/*
+ * A little factory function to create a protocol
+ * instance of the correct type.
+ */
+static Private_Repl_Protocol *
+private_protocol_factory(Repl_Protocol *rp, int type)
+{
+ Private_Repl_Protocol *prp;
+ switch (type)
+ {
+ case PROTOCOL_5_INCREMENTAL:
+ prp = Repl_5_Inc_Protocol_new(rp);
+ break;
+ case PROTOCOL_5_TOTAL:
+ prp = Repl_5_Tot_Protocol_new(rp);
+ break;
+ }
+ return prp;
+}
diff --git a/ldap/servers/plugins/replication/repl5_protocol_util.c b/ldap/servers/plugins/replication/repl5_protocol_util.c
new file mode 100644
index 00000000..16f65485
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl5_protocol_util.c
@@ -0,0 +1,468 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+/* repl5_protocol_util.c */
+/*
+
+Code common to both incremental and total protocols.
+
+*/
+
+#include "repl5.h"
+#include "repl5_prot_private.h"
+
+
+/*
+ * Obtain a current CSN (e.g. one that would have been
+ * generated for an operation occurring at this time)
+ * for a given replica.
+ */
+CSN *
+get_current_csn(Slapi_DN *replarea_sdn)
+{
+ Object *replica_obj;
+ Replica *replica;
+ Object *gen_obj;
+ CSNGen *gen;
+ CSN *current_csn = NULL;
+
+ if (NULL != replarea_sdn)
+ {
+ replica_obj = replica_get_replica_from_dn(replarea_sdn);
+ if (NULL != replica_obj)
+ {
+ replica = object_get_data(replica_obj);
+ if (NULL != replica)
+ {
+ gen_obj = replica_get_csngen(replica);
+ if (NULL != gen_obj)
+ {
+ gen = (CSNGen *)object_get_data(gen_obj);
+ if (NULL != gen)
+ {
+ if (csngen_new_csn(gen, &current_csn,
+ PR_FALSE /* notify */) != CSN_SUCCESS)
+ {
+ current_csn = NULL;
+
+ }
+ object_release(gen_obj);
+ }
+ }
+ }
+ }
+ }
+ return current_csn;
+}
+
+
+/*
+ * Acquire exclusive access to a replica. Send a start replication extended
+ * operation to the replica. The response will contain a success code, and
+ * optionally the replica's update vector if acquisition is successful.
+ * This function returns one of the following:
+ * ACQUIRE_SUCCESS - the replica was acquired, and we have exclusive update access
+ * ACQUIRE_REPLICA_BUSY - another master was updating the replica
+ * ACQUIRE_FATAL_ERROR - something bad happened, and it's not likely to improve
+ * if we wait.
+ * ACQUIRE_TRANSIENT_ERROR - something bad happened, but it's probably worth
+ * another try after waiting a while.
+ * If ACQUIRE_SUCCESS is returned, then ruv will point to the replica's update
+ * vector. It's possible that the replica does something goofy and doesn't
+ * return us an update vector, so be prepared for ruv to be NULL (but this is
+ * an error).
+ */
+int
+acquire_replica(Private_Repl_Protocol *prp, char *prot_oid, RUV **ruv)
+{
+ int return_value;
+ ConnResult crc;
+ Repl_Connection *conn;
+
+ PR_ASSERT(prp && prot_oid);
+
+ if (prp->replica_acquired) /* we already acquire replica */
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Remote replica already acquired\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = ACQUIRE_FATAL_ERROR;
+ return ACQUIRE_SUCCESS;
+ }
+
+ if (NULL != ruv)
+ {
+ ruv_destroy ( ruv );
+ }
+
+ if (strcmp(prot_oid, REPL_NSDS50_INCREMENTAL_PROTOCOL_OID) == 0)
+ {
+ Replica *replica;
+ Object *supl_ruv_obj, *cons_ruv_obj;
+ PRBool is_newer = PR_FALSE;
+
+ object_acquire(prp->replica_object);
+ replica = object_get_data(prp->replica_object);
+ supl_ruv_obj = replica_get_ruv ( replica );
+ cons_ruv_obj = agmt_get_consumer_ruv ( prp->agmt );
+ is_newer = ruv_is_newer ( supl_ruv_obj, cons_ruv_obj );
+ if ( supl_ruv_obj ) object_release ( supl_ruv_obj );
+ if ( cons_ruv_obj ) object_release ( cons_ruv_obj );
+ object_release (prp->replica_object);
+ replica = NULL;
+
+ if (is_newer == PR_FALSE) {
+ prp->last_acquire_response_code = NSDS50_REPL_UPTODATE;
+ return ACQUIRE_CONSUMER_WAS_UPTODATE;
+ }
+ }
+
+ prp->last_acquire_response_code = NSDS50_REPL_REPLICA_NO_RESPONSE;
+
+ /* Get the connection */
+ conn = prp->conn;
+
+ crc = conn_connect(conn);
+ if (CONN_OPERATION_FAILED == crc)
+ {
+ return_value = ACQUIRE_TRANSIENT_ERROR;
+ }
+ else if (CONN_SSL_NOT_ENABLED == crc)
+ {
+ return_value = ACQUIRE_FATAL_ERROR;
+ }
+ else
+ {
+ /* we don't want the timer to go off in the middle of an operation */
+ conn_cancel_linger(conn);
+ /* Does the remote replica support the 5.0 protocol? */
+ crc = conn_replica_supports_ds5_repl(conn);
+ if (CONN_DOES_NOT_SUPPORT_DS5_REPL == crc)
+ {
+ return_value = ACQUIRE_FATAL_ERROR;
+ }
+ else if (CONN_NOT_CONNECTED == crc || CONN_OPERATION_FAILED == crc)
+ {
+ /* We don't know anything about the remote replica. Try again later. */
+ return_value = ACQUIRE_TRANSIENT_ERROR;
+ }
+ else
+ {
+ /* Good to go. Start the protocol. */
+ CSN *current_csn = NULL;
+ struct berval *retdata = NULL;
+ char *retoid = NULL;
+ Slapi_DN *replarea_sdn;
+
+ /* Obtain a current CSN */
+ replarea_sdn = agmt_get_replarea(prp->agmt);
+ current_csn = get_current_csn(replarea_sdn);
+ if (NULL != current_csn)
+ {
+ struct berval *payload = NSDS50StartReplicationRequest_new(
+ prot_oid, slapi_sdn_get_ndn(replarea_sdn),
+ NULL /* XXXggood need to provide referral(s) */, current_csn);
+ /* JCMREPL - Need to extract the referrals from the RUV */
+ csn_free(&current_csn);
+ current_csn = NULL;
+ crc = conn_send_extended_operation(conn,
+ REPL_START_NSDS50_REPLICATION_REQUEST_OID, payload, &retoid,
+ &retdata, NULL /* update control */, NULL /* returned controls */);
+ ber_bvfree(payload);
+ payload = NULL;
+ /* Look at the response we got. */
+ if (CONN_OPERATION_SUCCESS == crc)
+ {
+ /*
+ * Extop was processed. Look at extop response to see if we're
+ * permitted to go ahead.
+ */
+ struct berval **ruv_bervals = NULL;
+ int extop_result;
+ int extop_rc = decode_repl_ext_response(retdata, &extop_result,
+ &ruv_bervals);
+ if (0 == extop_rc)
+ {
+ prp->last_acquire_response_code = extop_result;
+ switch (extop_result)
+ {
+ /* XXXggood handle other error codes here */
+ case NSDS50_REPL_INTERNAL_ERROR:
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Unable to acquire replica: "
+ "an internal error occurred on the remote replica. "
+ "Replication is aborting.\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = ACQUIRE_FATAL_ERROR;
+ break;
+ case NSDS50_REPL_PERMISSION_DENIED:
+ /* Not allowed to send updates */
+ {
+ char *repl_binddn = agmt_get_binddn(prp->agmt);
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Unable to acquire replica: permission denied. "
+ "The bind dn \"%s\" does not have permission to "
+ "supply replication updates to the replica. "
+ "Will retry later.\n",
+ agmt_get_long_name(prp->agmt), repl_binddn);
+ slapi_ch_free((void **)&repl_binddn);
+ return_value = ACQUIRE_TRANSIENT_ERROR;
+ break;
+ }
+ case NSDS50_REPL_NO_SUCH_REPLICA:
+ /* There is no such replica on the consumer */
+ {
+ Slapi_DN *repl_root = agmt_get_replarea(prp->agmt);
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Unable to acquire replica: there is no "
+ "replicated area \"%s\" on the consumer server. "
+ "Replication is aborting.\n",
+ agmt_get_long_name(prp->agmt),
+ slapi_sdn_get_dn(repl_root));
+ slapi_sdn_free(&repl_root);
+ return_value = ACQUIRE_FATAL_ERROR;
+ break;
+ }
+ case NSDS50_REPL_EXCESSIVE_CLOCK_SKEW:
+ /* Large clock skew between the consumer and the supplier */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Unable to acquire replica: "
+ "Excessive clock skew between the supplier and "
+ "the consumer. Replication is aborting.\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = ACQUIRE_FATAL_ERROR;
+ break;
+ case NSDS50_REPL_DECODING_ERROR:
+ /* We sent something the replica couldn't understand. */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Unable to acquire replica: "
+ "the consumer was unable to decode the "
+ "startReplicationRequest extended operation sent by the "
+ "supplier. Replication is aborting.\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = ACQUIRE_FATAL_ERROR;
+ break;
+ case NSDS50_REPL_REPLICA_BUSY:
+ /* Someone else is updating the replica. Try later. */
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: Unable to acquire replica: "
+ "the replica is currently being updated"
+ "by another supplier. Will try later\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = ACQUIRE_REPLICA_BUSY;
+ break;
+ case NSDS50_REPL_LEGACY_CONSUMER:
+ /* remote replica is a legacy consumer */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Unable to acquire replica: the replica "
+ "is supplied by a legacy supplier. "
+ "Replication is aborting.\n", agmt_get_long_name(prp->agmt));
+ return_value = ACQUIRE_FATAL_ERROR;
+ break;
+ case NSDS50_REPL_REPLICAID_ERROR:
+ /* remote replica detected a duplicate ReplicaID */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Unable to aquire replica: the replica "
+ "has the same Replica ID as this one. "
+ "Replication is aborting.\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = ACQUIRE_FATAL_ERROR;
+ break;
+ case NSDS50_REPL_REPLICA_READY:
+ /* We've acquired the replica. */
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: Replica was successfully acquired.\n",
+ agmt_get_long_name(prp->agmt));
+ /* Parse the update vector */
+ if (NULL != ruv_bervals && NULL != ruv)
+ {
+ if (ruv_init_from_bervals(ruv_bervals, ruv) != RUV_SUCCESS)
+ {
+ /* Couldn't parse the update vector */
+ *ruv = NULL;
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Warning: acquired replica, "
+ "but could not parse update vector. "
+ "The replica must be reinitialized.\n",
+ agmt_get_long_name(prp->agmt));
+ }
+ }
+
+ /* Save consumer's RUV in the replication agreement.
+ It is used by the changelog trimming code */
+ if (ruv && *ruv)
+ agmt_set_consumer_ruv (prp->agmt, *ruv);
+
+ return_value = ACQUIRE_SUCCESS;
+ break;
+ default:
+ return_value = ACQUIRE_FATAL_ERROR;
+ }
+ }
+ else
+ {
+ /* Couldn't parse the response */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Unable to parse the response to the "
+ "startReplication extended operation. "
+ "Replication is aborting.\n",
+ agmt_get_long_name(prp->agmt));
+ prp->last_acquire_response_code = NSDS50_REPL_INTERNAL_ERROR;
+ return_value = ACQUIRE_FATAL_ERROR;
+ }
+ if (NULL != ruv_bervals)
+ ber_bvecfree(ruv_bervals);
+ }
+ else
+ {
+ int operation, error;
+ conn_get_error(conn, &operation, &error);
+
+ /* Couldn't send the extended operation */
+ return_value = ACQUIRE_TRANSIENT_ERROR; /* XXX right return value? */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Unable to send a startReplication "
+ "extended operation to consumer (%s). Will retry later.\n",
+ agmt_get_long_name(prp->agmt),
+ error ? ldap_err2string(error) : "unknown error");
+ }
+ }
+ else
+ {
+ /* Couldn't get a current CSN */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Unable to obtain current CSN. "
+ "Replication is aborting.\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = ACQUIRE_FATAL_ERROR;
+ }
+ slapi_sdn_free(&replarea_sdn);
+ if (NULL != retoid)
+ ldap_memfree(retoid);
+ if (NULL != retdata)
+ ber_bvfree(retdata);
+ }
+ }
+
+ if (ACQUIRE_SUCCESS != return_value)
+ {
+ /* could not acquire the replica, so reinstate the linger timer, since this
+ means we won't call release_replica, which also reinstates the timer */
+ conn_start_linger(conn);
+ }
+ else
+ {
+ /* replica successfully acquired */
+ prp->replica_acquired = PR_TRUE;
+ }
+
+ return return_value;
+}
+
+
+/*
+ * Release a replica by sending an "end replication" extended request.
+ */
+void
+release_replica(Private_Repl_Protocol *prp)
+{
+ int rc;
+ struct berval *retdata = NULL;
+ char *retoid = NULL;
+ struct berval *payload = NULL;
+ Slapi_DN *replarea_sdn = NULL;
+
+ PR_ASSERT(NULL != prp);
+ PR_ASSERT(NULL != prp->conn);
+
+ if (!prp->replica_acquired)
+ return;
+
+ replarea_sdn = agmt_get_replarea(prp->agmt);
+ payload = NSDS50EndReplicationRequest_new((char *)slapi_sdn_get_dn(replarea_sdn)); /* XXXggood had to cast away const */
+ slapi_sdn_free(&replarea_sdn);
+ rc = conn_send_extended_operation(prp->conn,
+ REPL_END_NSDS50_REPLICATION_REQUEST_OID, payload, &retoid,
+ &retdata, NULL /* update control */, NULL /* returned controls */);
+ if (0 != rc)
+ {
+ int operation, error;
+ conn_get_error(prp->conn, &operation, &error);
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Warning: unable to send endReplication extended operation (%s)\n",
+ agmt_get_long_name(prp->agmt),
+ error ? ldap_err2string(error) : "unknown error");
+ }
+ else
+ {
+ struct berval **ruv_bervals = NULL; /* Shouldn't actually be returned */
+ int extop_result;
+ int extop_rc = decode_repl_ext_response(retdata, &extop_result,
+ (struct berval ***)&ruv_bervals);
+ if (0 == extop_rc)
+ {
+ if (NSDS50_REPL_REPLICA_RELEASE_SUCCEEDED == extop_result)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: Successfully released consumer\n", agmt_get_long_name(prp->agmt));
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Unable to release consumer: response code %d\n",
+ agmt_get_long_name(prp->agmt), extop_result);
+ /* disconnect from the consumer so that it does not stay locked */
+ conn_disconnect (prp->conn);
+ }
+ }
+ else
+ {
+ /* Couldn't parse the response */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Warning: Unable to parse the response "
+ " to the endReplication extended operation.\n",
+ agmt_get_long_name(prp->agmt));
+ }
+ if (NULL != ruv_bervals)
+ ber_bvecfree(ruv_bervals);
+ /* XXXggood free ruv_bervals if we got them for some reason */
+ }
+ if (NULL != payload)
+ ber_bvfree(payload);
+ if (NULL != retoid)
+ ldap_memfree(retoid);
+ if (NULL != retdata)
+ ber_bvfree(retdata);
+
+ /* replica is released, start the linger timer on the connection, which
+ was stopped in acquire_replica */
+ conn_start_linger(prp->conn);
+
+ prp->replica_acquired = PR_FALSE;
+}
+
+/* converts consumer's response to a string */
+char *
+protocol_response2string (int response)
+{
+ switch (response)
+ {
+ case NSDS50_REPL_REPLICA_READY: return "replica acquired";
+ case NSDS50_REPL_REPLICA_BUSY: return "replica busy";
+ case NSDS50_REPL_EXCESSIVE_CLOCK_SKEW: return "excessive clock skew";
+ case NSDS50_REPL_PERMISSION_DENIED: return "permission denied";
+ case NSDS50_REPL_DECODING_ERROR: return "decoding error";
+ case NSDS50_REPL_UNKNOWN_UPDATE_PROTOCOL: return "unknown update protocol";
+ case NSDS50_REPL_NO_SUCH_REPLICA: return "no such replica";
+ case NSDS50_REPL_BELOW_PURGEPOINT: return "csn below purge point";
+ case NSDS50_REPL_INTERNAL_ERROR: return "internal error";
+ case NSDS50_REPL_REPLICA_RELEASE_SUCCEEDED: return "replica released";
+ case NSDS50_REPL_LEGACY_CONSUMER: return "replica is a legacy consumer";
+ case NSDS50_REPL_REPLICAID_ERROR: return "duplicate replica ID detected";
+ case NSDS50_REPL_UPTODATE: return "no change to send";
+ default: return "unknown error";
+ }
+}
diff --git a/ldap/servers/plugins/replication/repl5_replica.c b/ldap/servers/plugins/replication/repl5_replica.c
new file mode 100644
index 00000000..5bc3e8ee
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl5_replica.c
@@ -0,0 +1,3387 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* repl5_replica.c */
+
+#include "slapi-plugin.h"
+#include "repl.h" /* ONREPL - this is bad */
+#include "repl5.h"
+#include "repl_shared.h"
+#include "csnpl.h"
+#include "cl5_api.h"
+
+/* from proto-slap.h */
+int g_get_shutdown();
+
+#define RUV_SAVE_INTERVAL (30 * 1000) /* 30 seconds */
+#define START_UPDATE_DELAY 2 /* 2 second */
+#define START_REAP_DELAY 3600 /* 1 hour */
+
+#define REPLICA_RDN "cn=replica"
+#define CHANGELOG_RDN "cn=legacy changelog"
+
+/*
+ * A replica is a locally-held copy of a portion of the DIT.
+ */
+struct replica {
+ Slapi_DN *repl_root; /* top of the replicated area */
+ char *repl_name; /* unique replica name */
+ PRBool new_name; /* new name was generated - need to be saved */
+ ReplicaUpdateDNList updatedn_list; /* list of dns with which a supplier should bind
+ to update this replica */
+ ReplicaType repl_type; /* is this replica read-only ? */
+ PRBool legacy_consumer; /* if true, this replica is supplied by 4.0 consumer */
+ char* legacy_purl; /* partial url of the legacy supplier */
+ ReplicaId repl_rid; /* replicaID */
+ Object *repl_ruv; /* replica update vector */
+ PRBool repl_ruv_dirty; /* Dirty flag for ruv */
+ CSNPL *min_csn_pl; /* Pending list for minimal CSN */
+ void *csn_pl_reg_id; /* registration assignment for csn callbacks */
+ unsigned long repl_state_flags; /* state flags */
+ PRUint32 repl_flags; /* persistent, externally visible flags */
+ PRLock *repl_lock; /* protects entire structure */
+ Slapi_Eq_Context repl_eqcxt_rs; /* context to cancel event that saves ruv */
+ Slapi_Eq_Context repl_eqcxt_tr; /* context to cancel event that reaps tombstones */
+ Object *repl_csngen; /* CSN generator for this replica */
+ PRBool repl_csn_assigned; /* Flag set when new csn is assigned. */
+ PRUint32 repl_purge_delay; /* When purgeable, CSNs are held on to for this many extra seconds */
+ PRBool tombstone_reap_stop; /* TRUE when the tombstone reaper should stop */
+ PRBool tombstone_reap_active; /* TRUE when the tombstone reaper is running */
+ long tombstone_reap_interval; /* Time in seconds between tombstone reaping */
+ Slapi_ValueSet *repl_referral; /* A list of administrator provided referral URLs */
+ PRBool state_update_inprogress; /* replica state is being updated */
+ PRLock *agmt_lock; /* protects agreement creation, start and stop */
+ char *locking_purl; /* supplier who has exclusive access */
+};
+
+
+typedef struct reap_callback_data
+{
+ int rc;
+ unsigned long num_entries;
+ unsigned long num_purged_entries;
+ CSN *purge_csn;
+ PRBool *tombstone_reap_stop;
+} reap_callback_data;
+
+
+/* Forward declarations of helper functions*/
+static Slapi_Entry* _replica_get_config_entry (const Slapi_DN *root);
+static int _replica_check_validity (const Replica *r);
+static int _replica_init_from_config (Replica *r, Slapi_Entry *e, char *errortext);
+static int _replica_update_entry (Replica *r, Slapi_Entry *e, char *errortext);
+static int _replica_configure_ruv (Replica *r, PRBool isLocked);
+static void _replica_update_state (time_t when, void *arg);
+static char * _replica_get_config_dn (const Slapi_DN *root);
+static char * _replica_type_as_string (const Replica *r);
+static int replica_create_ruv_tombstone(Replica *r);
+static void assign_csn_callback(const CSN *csn, void *data);
+static void abort_csn_callback(const CSN *csn, void *data);
+static void eq_cb_reap_tombstones(time_t when, void *arg);
+static CSN *_replica_get_purge_csn_nolock (const Replica *r);
+static void replica_get_referrals_nolock (const Replica *r, char ***referrals);
+static void replica_clear_legacy_referrals (const Slapi_DN *repl_root_sdn, char **referrals, const char *state);
+static void replica_remove_legacy_attr (const Slapi_DN *repl_root_sdn, const char *attr);
+static int replica_log_ruv_elements_nolock (const Replica *r);
+static void replica_replace_ruv_tombstone(Replica *r);
+static void start_agreements_for_replica (Replica *r, PRBool start);
+
+/* Allocates new replica and reads its state and state of its component from
+ * various parts of the DIT.
+ */
+Replica *
+replica_new(const Slapi_DN *root)
+{
+ Replica *r = NULL;
+ Slapi_Entry *e = NULL;
+ char errorbuf[BUFSIZ];
+ char ebuf[BUFSIZ];
+
+ PR_ASSERT (root);
+
+ /* check if there is a replica associated with the tree */
+ e = _replica_get_config_entry (root);
+ if (e)
+ {
+ errorbuf[0] = '\0';
+ r = replica_new_from_entry(e, errorbuf,
+ PR_FALSE /* not a newly added entry */);
+
+ if (NULL == r)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Unable to "
+ "configure replica %s: %s\n",
+ escape_string(slapi_sdn_get_dn(root), ebuf),
+ errorbuf);
+ }
+
+ slapi_entry_free (e);
+ }
+
+ return r;
+}
+
+/* constructs the replica object from the newly added entry */
+Replica *
+replica_new_from_entry (Slapi_Entry *e, char *errortext, PRBool is_add_operation)
+{
+ int rc = 0;
+ Replica *r;
+ RUV *ruv;
+ char *repl_name = NULL;
+
+ if (e == NULL)
+ {
+ if (NULL != errortext)
+ {
+ sprintf (errortext, "NULL entry");
+ }
+ return NULL;
+ }
+
+ r = (Replica *)slapi_ch_calloc(1, sizeof(Replica));
+
+ if ((r->repl_lock = PR_NewLock()) == NULL)
+ {
+ if (NULL != errortext)
+ {
+ sprintf (errortext, "failed to create replica lock");
+ }
+ rc = -1;
+ goto done;
+ }
+
+ if ((r->agmt_lock = PR_NewLock()) == NULL)
+ {
+ if (NULL != errortext)
+ {
+ sprintf (errortext, "failed to create replica lock");
+ }
+ rc = -1;
+ goto done;
+ }
+
+ /* read parameters from the replica config entry */
+ rc = _replica_init_from_config (r, e, errortext);
+ if (rc != 0)
+ {
+ goto done;
+ }
+
+ /* configure ruv */
+ rc = _replica_configure_ruv (r, PR_FALSE);
+ if (rc != 0)
+ {
+ goto done;
+ }
+
+ /* If smallest csn exists in RUV for our local replica, it's ok to begin iteration */
+ ruv = (RUV*) object_get_data (r->repl_ruv);
+ PR_ASSERT (ruv);
+
+ if (is_add_operation)
+ {
+ /*
+ * This is called by an ldap add operation.
+ * Update the entry to contain information generated
+ * during replica initialization
+ */
+ rc = _replica_update_entry (r, e, errortext);
+ }
+ else
+ {
+ /*
+ * Entry is already in dse.ldif - update it on the disk
+ * (done by the update state event scheduled below)
+ */
+ }
+ if (rc != 0)
+ goto done;
+
+ /* ONREPL - the state update can occur before the entry is added to the DIT.
+ In that case the updated would fail but nothing bad would happen. The next
+ scheduled update would save the state */
+ repl_name = slapi_ch_strdup (r->repl_name);
+ r->repl_eqcxt_rs = slapi_eq_repeat(_replica_update_state, repl_name,
+ current_time () + START_UPDATE_DELAY, RUV_SAVE_INTERVAL);
+
+ if (r->tombstone_reap_interval > 0)
+ {
+ /*
+ * Reap Tombstone should be started some time after the plugin started.
+ * This will allow the server to fully start before consuming resources.
+ */
+ repl_name = slapi_ch_strdup (r->repl_name);
+ r->repl_eqcxt_tr = slapi_eq_repeat(eq_cb_reap_tombstones, repl_name, current_time() + START_REAP_DELAY, 1000 * r->tombstone_reap_interval);
+ }
+
+ if (r->legacy_consumer)
+ {
+ char ebuf[BUFSIZ];
+
+ legacy_consumer_init_referrals (r);
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "replica_new_from_entry: "
+ "replica for %s was configured as legacy consumer\n",
+ escape_string(slapi_sdn_get_dn(r->repl_root),ebuf));
+ }
+
+done:
+ if (rc != 0 && r)
+ {
+ replica_destroy ((void**)&r);
+ }
+
+ return r;
+}
+
+
+void
+replica_flush(Replica *r)
+{
+ PR_ASSERT(NULL != r);
+ if (NULL != r)
+ {
+ PR_Lock(r->repl_lock);
+ /* Make sure we dump the CSNGen state */
+ r->repl_csn_assigned = PR_TRUE;
+ PR_Unlock(r->repl_lock);
+ /* This function take the Lock Inside */
+ /* And also write the RUV */
+ _replica_update_state((time_t)0, r->repl_name);
+ }
+}
+
+
+/*
+ * Deallocate a replica. arg should point to the address of a
+ * pointer that points to a replica structure.
+ */
+void
+replica_destroy(void **arg)
+{
+ Replica *r;
+ void *repl_name;
+
+ if (arg == NULL)
+ return;
+
+ r = *((Replica **)arg);
+
+ PR_ASSERT(r);
+
+ slapi_log_error (SLAPI_LOG_REPL, NULL, "replica_destroy\n");
+
+ /*
+ * The function will not be called unless the refcnt of its
+ * wrapper object is 0. Hopefully this refcnt could sync up
+ * this destruction and the events such as tombstone reap
+ * and ruv updates.
+ */
+
+ if (r->repl_eqcxt_rs)
+ {
+ repl_name = slapi_eq_get_arg (r->repl_eqcxt_rs);
+ slapi_ch_free (&repl_name);
+ slapi_eq_cancel(r->repl_eqcxt_rs);
+ r->repl_eqcxt_rs = NULL;
+ }
+
+ if (r->repl_eqcxt_tr)
+ {
+ repl_name = slapi_eq_get_arg (r->repl_eqcxt_tr);
+ slapi_ch_free (&repl_name);
+ slapi_eq_cancel(r->repl_eqcxt_tr);
+ r->repl_eqcxt_tr = NULL;
+ }
+
+ if (r->repl_root)
+ {
+ slapi_sdn_free(&r->repl_root);
+ }
+
+ slapi_ch_free_string(&r->locking_purl);
+
+ if (r->updatedn_list)
+ {
+ replica_updatedn_list_free(r->updatedn_list);
+ r->updatedn_list = NULL;
+ }
+
+ /* slapi_ch_free accepts NULL pointer */
+ slapi_ch_free ((void**)&r->repl_name);
+ slapi_ch_free ((void**)&r->legacy_purl);
+
+ if (r->repl_lock)
+ {
+ PR_DestroyLock(r->repl_lock);
+ r->repl_lock = NULL;
+ }
+
+ if (r->agmt_lock)
+ {
+ PR_DestroyLock(r->agmt_lock);
+ r->agmt_lock = NULL;
+ }
+
+ if(NULL != r->repl_ruv)
+ {
+ object_release(r->repl_ruv);
+ }
+
+ if(NULL != r->repl_csngen)
+ {
+ if (r->csn_pl_reg_id)
+ {
+ csngen_unregister_callbacks((CSNGen *)object_get_data (r->repl_csngen), r->csn_pl_reg_id);
+ }
+ object_release(r->repl_csngen);
+ }
+
+ if (NULL != r->repl_referral)
+ {
+ slapi_valueset_free(r->repl_referral);
+ }
+
+ if (NULL != r->min_csn_pl)
+ {
+ csnplFree(&r->min_csn_pl);;
+ }
+
+ slapi_ch_free((void **)arg);
+}
+
+/*
+ * Attempt to obtain exclusive access to replica (advisory only)
+ *
+ * Returns PR_TRUE if exclusive access was granted,
+ * PR_FALSE otherwise
+ * The parameter isInc tells whether or not the replica is being
+ * locked for an incremental update session - if the replica is
+ * successfully locked, this value is used - if the replica is already
+ * in use, this value will be set to TRUE or FALSE, depending on what
+ * type of update session has the replica in use currently
+ * locking_purl is the supplier who is attempting to acquire access
+ * current_purl is the supplier who already has access, if any
+ */
+PRBool
+replica_get_exclusive_access(Replica *r, PRBool *isInc, int connid, int opid,
+ const char *locking_purl,
+ char **current_purl)
+{
+ char ebuf[BUFSIZ];
+ PRBool rval = PR_TRUE;
+
+ PR_ASSERT(r);
+
+ PR_Lock(r->repl_lock);
+ if (r->repl_state_flags & REPLICA_IN_USE)
+ {
+ if (isInc)
+ *isInc = (r->repl_state_flags & REPLICA_INCREMENTAL_IN_PROGRESS);
+
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "conn=%d op=%d repl=\"%s\": "
+ "Replica in use locking_purl=%s\n",
+ connid, opid,
+ escape_string(slapi_sdn_get_dn(r->repl_root),ebuf),
+ r->locking_purl ? r->locking_purl : "unknown");
+ rval = PR_FALSE;
+ if (current_purl)
+ {
+ *current_purl = slapi_ch_strdup(r->locking_purl);
+ }
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "conn=%d op=%d repl=\"%s\": Acquired replica\n",
+ connid, opid,
+ escape_string(slapi_sdn_get_dn(r->repl_root),ebuf));
+ r->repl_state_flags |= REPLICA_IN_USE;
+ if (isInc && *isInc)
+ {
+ r->repl_state_flags |= REPLICA_INCREMENTAL_IN_PROGRESS;
+ }
+ else
+ {
+ /* if connid or opid != 0, it's a total update */
+ /* Both set to 0 means we're disabling replication */
+ if (connid || opid)
+ {
+ r->repl_state_flags |= REPLICA_TOTAL_IN_PROGRESS;
+ }
+ }
+ slapi_ch_free_string(&r->locking_purl);
+ r->locking_purl = slapi_ch_strdup(locking_purl);
+ }
+ PR_Unlock(r->repl_lock);
+ return rval;
+}
+
+/*
+ * Relinquish exclusive access to the replica
+ */
+void
+replica_relinquish_exclusive_access(Replica *r, int connid, int opid)
+{
+ char ebuf[BUFSIZ];
+ PRBool isInc;
+
+ PR_ASSERT(r);
+
+ PR_Lock(r->repl_lock);
+ isInc = (r->repl_state_flags & REPLICA_INCREMENTAL_IN_PROGRESS);
+ /* check to see if the replica is in use and log a warning if not */
+ if (!(r->repl_state_flags & REPLICA_IN_USE))
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "conn=%d op=%d repl=\"%s\": "
+ "Replica not in use\n",
+ connid, opid,
+ escape_string(slapi_sdn_get_dn(r->repl_root),ebuf));
+ } else {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "conn=%d op=%d repl=\"%s\": "
+ "Released replica\n",
+ connid, opid,
+ escape_string(slapi_sdn_get_dn(r->repl_root),ebuf));
+ slapi_ch_free_string(&r->locking_purl);
+ r->repl_state_flags &= ~(REPLICA_IN_USE);
+ if (isInc)
+ r->repl_state_flags &= ~(REPLICA_INCREMENTAL_IN_PROGRESS);
+ else
+ r->repl_state_flags &= ~(REPLICA_TOTAL_IN_PROGRESS);
+ }
+ PR_Unlock(r->repl_lock);
+}
+
+/*
+ * Returns root of the replicated area
+ */
+PRBool
+replica_get_tombstone_reap_active(const Replica *r)
+{
+ PR_ASSERT(r);
+
+ return(r->tombstone_reap_active);
+}
+
+/*
+ * Returns root of the replicated area
+ */
+const Slapi_DN *
+replica_get_root(const Replica *r) /* ONREPL - should we return copy instead? */
+{
+ PR_ASSERT(r);
+
+ /* replica root never changes so we don't have to lock */
+ return(r->repl_root);
+}
+
+/*
+ * Returns normalized dn of the root of the replicated area
+ */
+const char *
+replica_get_name(const Replica *r) /* ONREPL - should we return copy instead? */
+{
+ PR_ASSERT(r);
+
+ /* replica name never changes so we don't have to lock */
+ return(r->repl_name);
+}
+
+/*
+ * Returns replicaid of this replica
+ */
+ReplicaId
+replica_get_rid (const Replica *r)
+{
+ ReplicaId rid;
+ PR_ASSERT(r);
+
+ PR_Lock(r->repl_lock);
+ rid = r->repl_rid;
+ PR_Unlock(r->repl_lock);
+ return rid;
+}
+
+/*
+ * Sets replicaid of this replica - should only be used when also changing the type
+ */
+void
+replica_set_rid (Replica *r, ReplicaId rid)
+{
+ PR_ASSERT(r);
+
+ PR_Lock(r->repl_lock);
+ r->repl_rid = rid;
+ PR_Unlock(r->repl_lock);
+}
+
+/* Returns true if replica was initialized through ORC or import;
+ * otherwise, false. An uninitialized replica should return
+ * LDAP_UNWILLING_TO_PERFORM to all client requests
+ */
+PRBool
+replica_is_initialized (const Replica *r)
+{
+ PR_ASSERT(r);
+ return (r->repl_ruv != NULL);
+}
+
+/*
+ * Returns refcounted object that contains RUV. The caller should release the
+ * object once it is no longer used. To release, call object_release
+ */
+Object *
+replica_get_ruv (const Replica *r)
+{
+ Object *ruv = NULL;
+
+ PR_ASSERT(r);
+
+ PR_Lock(r->repl_lock);
+
+ PR_ASSERT (r->repl_ruv);
+
+ object_acquire (r->repl_ruv);
+
+ ruv = r->repl_ruv;
+
+ PR_Unlock(r->repl_lock);
+
+ return ruv;
+}
+
+/*
+ * Sets RUV vector. This function should be called during replica
+ * (re)initialization. During normal operation, the RUV is read from
+ * the root of the replicated in the replica_new call
+ */
+void
+replica_set_ruv (Replica *r, RUV *ruv)
+{
+ PR_ASSERT(r && ruv);
+
+ PR_Lock(r->repl_lock);
+
+ if(NULL != r->repl_ruv)
+ {
+ object_release(r->repl_ruv);
+ }
+
+ /* if the local replica is not in the RUV and it is writable - add it
+ and reinitialize min_csn pending list */
+ if (r->repl_type == REPLICA_TYPE_UPDATABLE)
+ {
+ CSN *csn = NULL;
+ if (r->min_csn_pl)
+ csnplFree (&r->min_csn_pl);
+
+ if (ruv_contains_replica (ruv, r->repl_rid))
+ {
+ ruv_get_smallest_csn_for_replica(ruv, r->repl_rid, &csn);
+ if (csn)
+ csn_free (&csn);
+ else
+ r->min_csn_pl = csnplNew ();
+ /* We need to make sure the local ruv element is the 1st. */
+ ruv_move_local_supplier_to_first(ruv, r->repl_rid);
+ }
+ else
+ {
+ r->min_csn_pl = csnplNew ();
+ /* To be sure that the local is in first */
+ ruv_add_index_replica(ruv, r->repl_rid, multimaster_get_local_purl(), 1);
+ }
+ }
+
+ r->repl_ruv = object_new((void*)ruv, (FNFree)ruv_destroy);
+ r->repl_ruv_dirty = PR_TRUE;
+
+ PR_Unlock(r->repl_lock);
+}
+
+/*
+ * Update one particular CSN in an RUV. This is meant to be called
+ * whenever (a) the server has processed a client operation and
+ * needs to update its CSN, or (b) the server is completing an
+ * inbound replication session operation, and needs to update its
+ * local RUV.
+ */
+void
+replica_update_ruv(Replica *r, const CSN *updated_csn, const char *replica_purl)
+{
+ char csn_str[CSN_STRSIZE];
+ char ebuf[BUFSIZ];
+
+ PR_ASSERT(NULL != r);
+ PR_ASSERT(NULL != updated_csn);
+#ifdef DEBUG
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "replica_update_ruv: csn %s\n",
+ csn_as_string(updated_csn, PR_FALSE, csn_str)); /* XXXggood remove debugging */
+#endif
+ if (NULL == r)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_update_ruv: replica "
+ "is NULL\n");
+ }
+ else if (NULL == updated_csn)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_update_ruv: csn "
+ "is NULL when updating replica %s\n", escape_string(slapi_sdn_get_dn(r->repl_root),ebuf));
+ }
+ else
+ {
+ RUV *ruv;
+ PR_Lock(r->repl_lock);
+
+ if (r->repl_ruv != NULL)
+ {
+ ruv = object_get_data(r->repl_ruv);
+ if (NULL != ruv)
+ {
+ ReplicaId rid = csn_get_replicaid(updated_csn);
+ if (rid == r->repl_rid)
+ {
+ if (NULL != r->min_csn_pl)
+ {
+ CSN *min_csn;
+ PRBool committed;
+ (void)csnplCommit(r->min_csn_pl, updated_csn);
+ min_csn = csnplGetMinCSN(r->min_csn_pl, &committed);
+ if (NULL != min_csn)
+ {
+ if (committed)
+ {
+ ruv_set_min_csn(ruv, min_csn, replica_purl);
+ csnplFree(&r->min_csn_pl);
+ }
+ csn_free(&min_csn);
+ }
+ }
+ }
+ /* Update max csn for local and remote replicas */
+ if (ruv_update_ruv (ruv, updated_csn, replica_purl, rid == r->repl_rid)
+ != RUV_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL,
+ repl_plugin_name, "replica_update_ruv: unable "
+ "to update RUV for replica %s, csn = %s\n",
+ escape_string(slapi_sdn_get_dn(r->repl_root),ebuf),
+ csn_as_string(updated_csn, PR_FALSE, csn_str));
+ }
+
+ r->repl_ruv_dirty = PR_TRUE;
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "replica_update_ruv: unable to get RUV object for replica "
+ "%s\n", escape_string(slapi_sdn_get_dn(r->repl_root),ebuf));
+ }
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_update_ruv: "
+ "unable to initialize RUV for replica %s\n",
+ escape_string(slapi_sdn_get_dn(r->repl_root),ebuf));
+ }
+ PR_Unlock(r->repl_lock);
+ }
+}
+
+/*
+ * Returns refcounted object that contains csn generator. The caller should release the
+ * object once it is no longer used. To release, call object_release
+ */
+Object *
+replica_get_csngen (const Replica *r)
+{
+ Object *csngen;
+
+ PR_ASSERT(r);
+
+ PR_Lock(r->repl_lock);
+
+ object_acquire (r->repl_csngen);
+ csngen = r->repl_csngen;
+
+ PR_Unlock(r->repl_lock);
+
+ return csngen;
+}
+
+/*
+ * Returns the replica type.
+ */
+ReplicaType
+replica_get_type (const Replica *r)
+{
+ PR_ASSERT(r);
+ return r->repl_type;
+}
+
+/*
+ * Sets the replica type.
+ */
+void
+replica_set_type (Replica *r, ReplicaType type)
+{
+ PR_ASSERT(r);
+
+ PR_Lock(r->repl_lock);
+ r->repl_type = type;
+ PR_Unlock(r->repl_lock);
+}
+
+/*
+ * Returns PR_TRUE if this replica is a consumer of 4.0 server
+ * and PR_FALSE otherwise
+ */
+PRBool
+replica_is_legacy_consumer (const Replica *r)
+{
+ PR_ASSERT(r);
+ return r->legacy_consumer;
+}
+
+/*
+ * Sets the replica type.
+ */
+void
+replica_set_legacy_consumer (Replica *r, PRBool legacy_consumer)
+{
+ int legacy2mmr;
+ Slapi_DN *repl_root_sdn = NULL;
+ char **referrals = NULL;
+ char *replstate = NULL;
+ PR_ASSERT(r);
+
+ PR_Lock(r->repl_lock);
+
+ legacy2mmr = r->legacy_consumer && !legacy_consumer;
+
+ /* making the server a regular 5.0 replica */
+ if (legacy2mmr)
+ {
+ slapi_ch_free ((void**)&r->legacy_purl);
+ /* Remove copiedFrom/copyingFrom attributes from the root entry */
+ /* set the right state in the mapping tree */
+ if (r->repl_type == REPLICA_TYPE_READONLY)
+ {
+ replica_get_referrals_nolock (r, &referrals);
+ replstate = STATE_UPDATE_REFERRAL;
+ }
+ else /* updateable */
+ {
+ replstate = STATE_BACKEND;
+ }
+ }
+
+ r->legacy_consumer = legacy_consumer;
+ repl_root_sdn = slapi_sdn_dup(r->repl_root);
+ PR_Unlock(r->repl_lock);
+
+ if (legacy2mmr)
+ {
+ replica_clear_legacy_referrals(repl_root_sdn, referrals, replstate);
+ /* Also change state of the mapping tree node and/or referrals */
+ replica_remove_legacy_attr (repl_root_sdn, type_copiedFrom);
+ replica_remove_legacy_attr (repl_root_sdn, type_copyingFrom);
+ }
+ charray_free(referrals);
+ slapi_sdn_free(&repl_root_sdn);
+}
+
+/* Gets partial url of the legacy supplier - applicable for legacy consumer only */
+char *
+replica_get_legacy_purl (const Replica *r)
+{
+ char *purl;
+
+ PR_Lock (r->repl_lock);
+
+ PR_ASSERT (r->legacy_consumer);
+
+ purl = slapi_ch_strdup (r->legacy_purl);
+
+ PR_Unlock (r->repl_lock);
+
+ return purl;
+}
+
+void
+replica_set_legacy_purl (Replica *r, const char *purl)
+{
+ PR_Lock (r->repl_lock);
+
+ PR_ASSERT (r->legacy_consumer);
+
+ /* slapi_ch_free accepts NULL pointer */
+ slapi_ch_free ((void**)&r->legacy_purl);
+
+ r->legacy_purl = slapi_ch_strdup (purl);
+
+ PR_Unlock (r->repl_lock);
+}
+
+/*
+ * Returns true if sdn is the same as updatedn and false otherwise
+ */
+PRBool
+replica_is_updatedn (const Replica *r, const Slapi_DN *sdn)
+{
+ PRBool result;
+
+ PR_ASSERT (r);
+
+ PR_Lock(r->repl_lock);
+
+ if (sdn == NULL)
+ {
+ result = (r->updatedn_list == NULL);
+ }
+ else if (r->updatedn_list == NULL)
+ {
+ result = PR_FALSE;
+ }
+ else
+ {
+ result = replica_updatedn_list_ismember(r->updatedn_list, sdn);
+ }
+
+ PR_Unlock(r->repl_lock);
+
+ return result;
+}
+
+/*
+ * Sets updatedn list for this replica
+ */
+void
+replica_set_updatedn (Replica *r, const Slapi_ValueSet *vs, int mod_op)
+{
+ PR_ASSERT (r);
+
+ PR_Lock(r->repl_lock);
+
+ if (!r->updatedn_list)
+ r->updatedn_list = replica_updatedn_list_new(NULL);
+
+ if (mod_op & LDAP_MOD_DELETE || vs == NULL ||
+ (0 == slapi_valueset_count(vs))) /* null value also causes list deletion */
+ replica_updatedn_list_delete(r->updatedn_list, vs);
+ else if (mod_op & LDAP_MOD_REPLACE)
+ replica_updatedn_list_replace(r->updatedn_list, vs);
+ else if (mod_op & LDAP_MOD_ADD)
+ replica_updatedn_list_add(r->updatedn_list, vs);
+
+ PR_Unlock(r->repl_lock);
+}
+
+/* gets current replica generation for this replica */
+char *replica_get_generation (const Replica *r)
+{
+ int rc = 0;
+ char *gen = NULL;
+
+ if (r)
+ {
+ PR_Lock(r->repl_lock);
+
+ PR_ASSERT (r->repl_ruv);
+
+ if (rc == 0)
+ gen = ruv_get_replica_generation ((RUV*)object_get_data (r->repl_ruv));
+
+ PR_Unlock(r->repl_lock);
+ }
+
+ return gen;
+}
+
+PRBool replica_is_flag_set (const Replica *r, PRUint32 flag)
+{
+ if (r)
+ return (r->repl_flags & flag);
+ else
+ return PR_FALSE;
+}
+
+void replica_set_flag (Replica *r, PRUint32 flag, PRBool clear)
+{
+ if (r == NULL)
+ return;
+
+ PR_Lock(r->repl_lock);
+
+ if (clear)
+ {
+ r->repl_flags &= ~flag;
+ }
+ else
+ {
+ r->repl_flags |= flag;
+ }
+
+ PR_Unlock(r->repl_lock);
+}
+
+void replica_replace_flags (Replica *r, PRUint32 flags)
+{
+ if (r)
+ {
+ PR_Lock(r->repl_lock);
+ r->repl_flags = flags;
+ PR_Unlock(r->repl_lock);
+ }
+}
+
+void
+replica_get_referrals(const Replica *r, char ***referrals)
+{
+ PR_Lock(r->repl_lock);
+ replica_get_referrals_nolock (r, referrals);
+ PR_Unlock(r->repl_lock);
+}
+
+void
+replica_set_referrals(Replica *r,const Slapi_ValueSet *vs)
+{
+ int ii = 0;
+ Slapi_Value *vv = NULL;
+ if (r->repl_referral == NULL)
+ {
+ r->repl_referral = slapi_valueset_new();
+ }
+ else
+ {
+ slapi_valueset_done(r->repl_referral);
+ }
+ slapi_valueset_set_valueset(r->repl_referral, vs);
+ /* make sure the DN is included in the referral LDAP URL */
+ if (r->repl_referral)
+ {
+ Slapi_ValueSet *newvs = slapi_valueset_new();
+ const char *repl_root = slapi_sdn_get_dn(r->repl_root);
+ int rootlen = strlen(repl_root);
+ ii = slapi_valueset_first_value(r->repl_referral, &vv);
+ while (vv)
+ {
+ const char *ref = slapi_value_get_string(vv);
+ struct ldap_url_desc *lud = NULL;
+ int myrc = ldap_url_parse(ref, &lud);
+ /* see if the dn is already in the referral URL */
+ if (myrc == LDAP_URL_ERR_NODN || !lud || !lud->lud_dn) {
+ /* add the dn */
+ Slapi_Value *newval = NULL;
+ int len = strlen(ref);
+ char *tmpref = NULL;
+ int need_slash = 0;
+ if (ref[len-1] != '/') {
+ len++; /* add another one for the slash */
+ need_slash = 1;
+ }
+ len += rootlen + 2;
+ tmpref = slapi_ch_malloc(len);
+ sprintf(tmpref, "%s%s%s", ref, (need_slash ? "/" : ""),
+ repl_root);
+ newval = slapi_value_new_string(tmpref);
+ slapi_ch_free_string(&tmpref); /* sv_new_string makes a copy */
+ slapi_valueset_add_value(newvs, newval);
+ slapi_value_free(&newval); /* s_vs_add_value makes a copy */
+ }
+ if (lud)
+ ldap_free_urldesc(lud);
+ ii = slapi_valueset_next_value(r->repl_referral, ii, &vv);
+ }
+ if (slapi_valueset_count(newvs) > 0) {
+ slapi_valueset_done(r->repl_referral);
+ slapi_valueset_set_valueset(r->repl_referral, newvs);
+ }
+ slapi_valueset_free(newvs); /* s_vs_set_vs makes a copy */
+ }
+}
+
+int
+replica_update_csngen_state (Replica *r, const RUV *ruv)
+{
+ int rc = 0;
+ CSNGen *gen;
+ CSN *csn = NULL;
+
+ PR_ASSERT (r && ruv);
+
+ rc = ruv_get_max_csn(ruv, &csn);
+ if (rc != RUV_SUCCESS)
+ {
+ return -1;
+ }
+
+ if (csn == NULL) /* ruv contains no csn - we are done */
+ {
+ return 0;
+ }
+
+ PR_Lock(r->repl_lock);
+
+ gen = (CSNGen *)object_get_data (r->repl_csngen);
+ PR_ASSERT (gen);
+
+ rc = csngen_adjust_time (gen, csn);
+ if (rc != CSN_SUCCESS)
+ {
+ rc = -1;
+ goto done;
+ }
+
+ rc = 0;
+
+done:
+
+ PR_Unlock(r->repl_lock);
+ if (csn)
+ csn_free (&csn);
+
+ return rc;
+}
+
+/*
+ * dumps replica state for debugging purpose
+ */
+void
+replica_dump(Replica *r)
+{
+ char *updatedn_list = NULL;
+ PR_ASSERT (r);
+
+ PR_Lock(r->repl_lock);
+
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "Replica state:\n");
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "\treplica root: %s\n",
+ slapi_sdn_get_ndn (r->repl_root));
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "\treplica type: %s\n",
+ _replica_type_as_string (r));
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "\treplica id: %d\n", r->repl_rid);
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "\tflags: %d\n", r->repl_flags);
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "\tstate flags: %d\n", r->repl_state_flags);
+ if (r->updatedn_list)
+ updatedn_list = replica_updatedn_list_to_string(r->updatedn_list, "\n\t\t");
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "\tupdate dn: %s\n",
+ updatedn_list? updatedn_list : "not configured");
+ slapi_ch_free_string(&updatedn_list);
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "\truv: %s configured and is %sdirty\n",
+ r->repl_ruv ? "" : "not", r->repl_ruv_dirty ? "" : "not ");
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "\tCSN generator: %s configured\n",
+ r->repl_csngen ? "" : "not");
+ /* JCMREPL - Dump Referrals */
+
+ PR_Unlock(r->repl_lock);
+}
+
+
+/*
+ * Return the CSN of the purge point. Any CSNs smaller than the
+ * purge point can be safely removed from entries within this
+ * this replica. Returns an allocated CSN that must be freed by
+ * the caller, or NULL if purging is disabled.
+ */
+
+CSN *
+replica_get_purge_csn(const Replica *r)
+{
+ CSN *csn;
+
+ PR_Lock(r->repl_lock);
+
+ csn= _replica_get_purge_csn_nolock(r);
+
+ PR_Unlock(r->repl_lock);
+
+ return csn;
+}
+
+
+/*
+ * This function logs a dummy entry for the smallest csn in the RUV.
+ * This is necessary because, to get the next change, we need to position
+ * changelog on the previous change. So this function insures that we always have one.
+ */
+
+/* ONREPL we will need to change this function to log all the
+ * ruv elements not just the smallest when changelog iteration
+ * algoritm changes to iterate replica by replica
+*/
+int
+replica_log_ruv_elements (const Replica *r)
+{
+ int rc = 0;
+
+ PR_ASSERT (r);
+
+ PR_Lock(r->repl_lock);
+
+ rc = replica_log_ruv_elements_nolock (r);
+
+ PR_Unlock(r->repl_lock);
+
+ return rc;
+}
+
+void
+consumer5_set_mapping_tree_state_for_replica(const Replica *r, RUV *supplierRuv)
+{
+ const Slapi_DN *repl_root_sdn= replica_get_root(r);
+ char **ruv_referrals= NULL;
+ char **replica_referrals= NULL;
+ RUV *ruv;
+ int state_backend = -1;
+ const char *mtn_state = NULL;
+
+ PR_Lock (r->repl_lock);
+
+ if ( supplierRuv == NULL )
+ {
+ ruv = (RUV*)object_get_data (r->repl_ruv);
+ PR_ASSERT (ruv);
+
+ ruv_referrals= ruv_get_referrals(ruv); /* ruv_referrals has to be free'd */
+ }
+ else
+ {
+ ruv_referrals = ruv_get_referrals(supplierRuv);
+ }
+
+ replica_get_referrals_nolock (r, &replica_referrals); /* replica_referrals has to be free'd */
+
+ /* JCMREPL - What if there's a Total update in progress? */
+ if( (r->repl_type==REPLICA_TYPE_READONLY) || (r->legacy_consumer) )
+ {
+ state_backend = 0;
+ }
+ else if (r->repl_type==REPLICA_TYPE_UPDATABLE)
+ {
+ state_backend = 1;
+ }
+ /* Unlock to avoid changing MTN state under repl lock */
+ PR_Unlock (r->repl_lock);
+
+ if(state_backend == 0 )
+ {
+ /* Read-Only - The mapping tree should be refering all update operations. */
+ mtn_state = STATE_UPDATE_REFERRAL;
+ }
+ else if (state_backend == 1)
+ {
+ /* Updatable - The mapping tree should be accepting all update operations. */
+ mtn_state = STATE_BACKEND;
+ }
+
+ /* JCMREPL - Check the return code. */
+ repl_set_mtn_state_and_referrals(repl_root_sdn, mtn_state, NULL,
+ ruv_referrals, replica_referrals);
+ charray_free(ruv_referrals);
+ charray_free(replica_referrals);
+}
+
+void
+replica_set_enabled (Replica *r, PRBool enable)
+{
+ char *repl_name = NULL;
+
+ PR_ASSERT (r);
+
+ PR_Lock (r->repl_lock);
+
+ if (enable)
+ {
+ if (r->repl_eqcxt_rs == NULL) /* event is not already registered */
+ {
+ repl_name = slapi_ch_strdup (r->repl_name);
+ r->repl_eqcxt_rs = slapi_eq_repeat(_replica_update_state, repl_name,
+ current_time() + START_UPDATE_DELAY, RUV_SAVE_INTERVAL);
+ }
+ }
+ else /* disable */
+ {
+ if (r->repl_eqcxt_rs) /* event is still registerd */
+ {
+ repl_name = slapi_eq_get_arg (r->repl_eqcxt_rs);
+ slapi_ch_free ((void**)&repl_name);
+ slapi_eq_cancel(r->repl_eqcxt_rs);
+ r->repl_eqcxt_rs = NULL;
+ }
+ }
+
+ PR_Unlock (r->repl_lock);
+}
+
+/* This function is generally called when replica's data store
+ is reloaded. It retrieves new RUV from the datastore. If new
+ RUV does not exist or if it is not as up to date as the purge RUV
+ of the corresponding changelog file, we need to remove */
+
+/* the function minimizes the use of replica lock where ever possible.
+ Locking replica lock while calling changelog functions
+ causes a deadlock because changelog calls replica functions that
+ that lock the same lock */
+
+int
+replica_reload_ruv (Replica *r)
+{
+ int rc = 0;
+ Object *old_ruv_obj = NULL, *new_ruv_obj = NULL;
+ RUV *upper_bound_ruv = NULL;
+ RUV *new_ruv = NULL;
+ Object *r_obj;
+
+ PR_ASSERT (r);
+
+ PR_Lock (r->repl_lock);
+
+ old_ruv_obj = r->repl_ruv;
+
+ r->repl_ruv = NULL;
+
+ rc = _replica_configure_ruv (r, PR_TRUE);
+
+ PR_Unlock (r->repl_lock);
+
+ if (rc != 0)
+ {
+ return rc;
+ }
+
+ /* check if there is a changelog and whether this replica logs changes */
+ if (cl5GetState () == CL5_STATE_OPEN && r->repl_flags & REPLICA_LOG_CHANGES)
+ {
+
+ /* Compare new ruv to the changelog's upper bound ruv. We could only keep
+ the existing changelog if its upper bound is the same as replica's RUV.
+ This is because if changelog has changes not in RUV, they will be
+ eventually sent to the consumer's which will cause a state mismatch
+ (because the supplier does not actually contain the changes in its data store.
+ If, on the other hand, the changelog is not as up to date as the supplier,
+ it is not really useful since out of sync consumer's can't be brought
+ up to date using this changelog and hence will need to be reinitialized */
+
+ /* replace ruv to make sure we work with the correct changelog file */
+ PR_Lock (r->repl_lock);
+
+ new_ruv_obj = r->repl_ruv;
+ r->repl_ruv = old_ruv_obj;
+
+ PR_Unlock (r->repl_lock);
+
+ rc = cl5GetUpperBoundRUV (r, &upper_bound_ruv);
+ if (rc != CL5_SUCCESS && rc != CL5_NOTFOUND)
+ {
+ return -1;
+ }
+
+ if (upper_bound_ruv)
+ {
+ new_ruv = object_get_data (new_ruv_obj);
+ PR_ASSERT (new_ruv);
+
+ /* ONREPL - there are more efficient ways to establish RUV equality.
+ However, because this is not in the critical path and we at most
+ have 2 elements in the RUV, this will not effect performance */
+
+ if (!ruv_covers_ruv (new_ruv, upper_bound_ruv) ||
+ !ruv_covers_ruv (upper_bound_ruv, new_ruv))
+ {
+ char ebuf[BUFSIZ];
+
+ /* create a temporary replica object to conform to the interface */
+ r_obj = object_new (r, NULL);
+
+ /* We can't use existing changelog - remove existing file */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_reload_ruv: "
+ "Warning: new data for replica %s does not match the data in the changelog.\n"
+ " Recreating the changelog file. This could affect replication with replica's "
+ " consumers in which case the consumers should be reinitialized.\n",
+ escape_string(slapi_sdn_get_dn(r->repl_root),ebuf));
+ rc = cl5DeleteDBSync (r_obj);
+
+ /* reinstate new ruv */
+ PR_Lock (r->repl_lock);
+
+ r->repl_ruv = new_ruv_obj;
+
+ object_release (r_obj);
+
+ if (rc == CL5_SUCCESS)
+ {
+ /* log changes to mark starting point for replication */
+ rc = replica_log_ruv_elements_nolock (r);
+ }
+
+ PR_Unlock (r->repl_lock);
+ }
+ else
+ {
+ /* we just need to reinstate new ruv */
+ PR_Lock (r->repl_lock);
+
+ r->repl_ruv = new_ruv_obj;
+
+ PR_Unlock (r->repl_lock);
+ }
+ }
+ else /* upper bound vector is not there - we have no changes logged */
+ {
+ /* reinstate new ruv */
+ PR_Lock (r->repl_lock);
+
+ r->repl_ruv = new_ruv_obj;
+
+ /* just log elements of the current RUV. This is to have
+ a starting point for iteration through the changes */
+ rc = replica_log_ruv_elements_nolock (r);
+
+ PR_Unlock (r->repl_lock);
+ }
+ }
+
+ if (rc == 0)
+ {
+ consumer5_set_mapping_tree_state_for_replica(r, NULL);
+ /* reset mapping tree referrals based on new local RUV */
+ }
+
+ if (old_ruv_obj)
+ object_release (old_ruv_obj);
+
+ if (upper_bound_ruv)
+ ruv_destroy (&upper_bound_ruv);
+
+ return rc;
+}
+
+/* this function is called during server startup for each replica
+ to check whether the replica's data was reloaded offline and
+ whether replica's changelog needs to be reinitialized */
+
+/* the function does not use replica lock but all functions it calls are
+ thread safe. Locking replica lock while calling changelog functions
+ causes a deadlock because changelog calls replica functions that
+ that lock the same lock */
+int replica_check_for_data_reload (Replica *r, void *arg)
+{
+ int rc = 0;
+ RUV *upper_bound_ruv = NULL;
+ RUV *r_ruv = NULL;
+ Object *r_obj, *ruv_obj;
+ int cl_cover_be, be_cover_cl;
+
+ PR_ASSERT (r);
+
+ /* check that we have a changelog and if this replica logs changes */
+ if (cl5GetState () == CL5_STATE_OPEN && r->repl_flags & REPLICA_LOG_CHANGES)
+ {
+ /* Compare new ruv to the purge ruv. If the new contains csns which
+ are smaller than those in purge ruv, we need to remove old and
+ create new changelog file for this replica. This is because we
+ will not have sufficient changes to incrementally update a consumer
+ to the current state of the supplier. */
+
+ rc = cl5GetUpperBoundRUV (r, &upper_bound_ruv);
+ if (rc != CL5_SUCCESS && rc != CL5_NOTFOUND)
+ {
+ return -1;
+ }
+
+ if (upper_bound_ruv)
+ {
+ ruv_obj = replica_get_ruv (r);
+ r_ruv = object_get_data (ruv_obj);
+ PR_ASSERT (r_ruv);
+
+ /* Compare new ruv to the changelog's upper bound ruv. We could only keep
+ the existing changelog if its upper bound is the same as replica's RUV.
+ This is because if changelog has changes not in RUV, they will be
+ eventually sent to the consumer's which will cause a state mismatch
+ (because the supplier does not actually contain the changes in its data store.
+ If, on the other hand, the changelog is not as up to date as the supplier,
+ it is not really useful since out of sync consumer's can't be brought
+ up to date using this changelog and hence will need to be reinitialized */
+
+ /*
+ * Actually we can ignore the scenario that the changelog's upper
+ * bound ruv covers data store's ruv for two reasons: (1) a change
+ * is always written to the changelog after it is committed to the
+ * data store; (2) a change will be ignored if the server has seen
+ * it before - this happens frequently at the beginning of replication
+ * sessions.
+ */
+
+ be_cover_cl = ruv_covers_ruv (r_ruv, upper_bound_ruv);
+ cl_cover_be = ruv_covers_ruv (upper_bound_ruv, r_ruv);
+ if (!cl_cover_be)
+ {
+ /* the data was reloaded and we can no longer use existing changelog */
+ char ebuf[BUFSIZ];
+
+ /* create a temporary replica object to conform to the interface */
+ r_obj = object_new (r, NULL);
+
+ /* We can't use existing changelog - remove existing file */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_check_for_data_reload: "
+ "Warning: data for replica %s was reloaded and it no longer matches the data "
+ "in the changelog (replica data %s changelog). Recreating the changelog file. This could affect replication "
+ "with replica's consumers in which case the consumers should be reinitialized.\n",
+ escape_string(slapi_sdn_get_dn(r->repl_root),ebuf),
+ ((!be_cover_cl && !cl_cover_be) ? "<>" : (!be_cover_cl ? "<" : ">")) );
+
+ rc = cl5DeleteDBSync (r_obj);
+
+ object_release (r_obj);
+
+ if (rc == CL5_SUCCESS)
+ {
+ /* log changes to mark starting point for replication */
+ rc = replica_log_ruv_elements (r);
+ }
+ }
+
+ object_release (ruv_obj);
+ }
+ else /* we have no changes currently logged for this replica */
+ {
+ /* log changes to mark starting point for replication */
+ rc = replica_log_ruv_elements (r);
+ }
+ }
+
+ if (rc == 0)
+ {
+ /* reset mapping tree referrals based on new local RUV */
+ consumer5_set_mapping_tree_state_for_replica(r, NULL);
+ }
+
+ if (upper_bound_ruv)
+ ruv_destroy (&upper_bound_ruv);
+
+ return rc;
+}
+
+/* Helper functions */
+/* reads replica configuration entry. The entry is the child of the
+ mapping tree node for the replica's backend */
+
+static Slapi_Entry*
+_replica_get_config_entry (const Slapi_DN *root)
+{
+ int rc = 0;
+ char *dn = NULL;
+ Slapi_Entry **entries;
+ Slapi_Entry *e = NULL;
+ Slapi_PBlock *pb = NULL;
+
+ dn = _replica_get_config_dn (root);
+ pb = slapi_pblock_new ();
+
+ slapi_search_internal_set_pb (pb, dn, LDAP_SCOPE_BASE, "objectclass=*", NULL, 0, NULL,
+ NULL, repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0);
+ slapi_search_internal_pb (pb);
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
+ if (rc == 0)
+ {
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries);
+ e = slapi_entry_dup (entries [0]);
+ }
+
+ slapi_free_search_results_internal(pb);
+ slapi_pblock_destroy (pb);
+ slapi_ch_free_string(&dn);
+
+ return e;
+}
+
+static int
+_replica_check_validity (const Replica *r)
+{
+ PR_ASSERT (r);
+
+ if (r->repl_root == NULL || r->repl_type == 0 || r->repl_rid == 0 ||
+ r->repl_rid > MAX_REPLICA_ID || r->repl_csngen == NULL || r->repl_name == NULL)
+ {
+ return -1;
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+/* replica configuration entry has the following format:
+ dn: cn=replica,<mapping tree node dn>
+ objectclass: top
+ objectclass: nsds5Replica
+ objectclass: extensibleObject
+ nsds5ReplicaRoot: <root of the replica>
+ nsds5ReplicaId: <replica id>
+ nsds5ReplicaType: <type of the replica: primary, read-write or read-only>
+ nsState: <state of the csn generator> missing the first time replica is started
+ nsds5ReplicaBindDN: <supplier update dn> consumers only
+ nsds5ReplicaReferral: <referral URL to updatable replica> consumers only
+ nsds5ReplicaPurgeDelay: <time, in seconds, to keep purgeable CSNs, 0 == keep forever>
+ nsds5ReplicaTombstonePurgeInterval: <time, in seconds, between tombstone purge runs, 0 == don't reap>
+ nsds5ReplicaLegacyConsumer: <TRUE | FALSE>
+
+ richm: changed slapi entry from const to editable - if the replica id is supplied for a read
+ only replica, we ignore it and replace the value with the READ_ONLY_REPLICA_ID
+ */
+static int
+_replica_init_from_config (Replica *r, Slapi_Entry *e, char *errortext)
+{
+ int rc;
+ Slapi_Attr *attr;
+ char *val;
+ CSNGen *gen;
+ char buf [BUFSIZ];
+ char *errormsg = errortext? errortext : buf;
+ Slapi_Attr *a = NULL;
+ char dnescape[BUFSIZ]; /* for escape_string */
+
+ PR_ASSERT (r && e);
+
+ /* get replica root */
+ val = slapi_entry_attr_get_charptr (e, attr_replicaRoot);
+ if (val == NULL)
+ {
+ sprintf (errormsg, "failed to retrieve %s attribute from (%s)\n",
+ attr_replicaRoot,
+ escape_string((char*)slapi_entry_get_dn ((Slapi_Entry*)e), dnescape));
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_replica_init_from_config: %s\n",
+ errormsg);
+
+ return -1;
+ }
+
+ r->repl_root = slapi_sdn_new_dn_passin (val);
+
+ /* get replica type */
+ val = slapi_entry_attr_get_charptr (e, attr_replicaType);
+ if (val)
+ {
+ r->repl_type = atoi(val);
+ slapi_ch_free ((void**)&val);
+ }
+ else
+ {
+ r->repl_type = REPLICA_TYPE_READONLY;
+ }
+
+ /* get legacy consumer flag */
+ val = slapi_entry_attr_get_charptr (e, type_replicaLegacyConsumer);
+ if (val)
+ {
+ if (strcasecmp (val, "on") == 0 || strcasecmp (val, "yes") == 0 ||
+ strcasecmp (val, "true") == 0 || strcasecmp (val, "1") == 0)
+ {
+ r->legacy_consumer = PR_TRUE;
+ }
+ else
+ {
+ r->legacy_consumer = PR_FALSE;
+ }
+
+ slapi_ch_free ((void**)&val);
+ }
+ else
+ {
+ r->legacy_consumer = PR_FALSE;
+ }
+
+ /* get replica flags */
+ r->repl_flags = slapi_entry_attr_get_ulong(e, attr_flags);
+
+ /* get replicaid */
+ /* the replica id is ignored for read only replicas and is set to the
+ special value READ_ONLY_REPLICA_ID */
+ if (r->repl_type == REPLICA_TYPE_READONLY)
+ {
+ r->repl_rid = READ_ONLY_REPLICA_ID;
+ slapi_entry_attr_set_uint(e, attr_replicaId, (unsigned int)READ_ONLY_REPLICA_ID);
+ }
+ /* a replica id is required for updatable and primary replicas */
+ else if (r->repl_type == REPLICA_TYPE_UPDATABLE ||
+ r->repl_type == REPLICA_TYPE_PRIMARY)
+ {
+ if ((val = slapi_entry_attr_get_charptr (e, attr_replicaId)))
+ {
+ int temprid = atoi (val);
+ slapi_ch_free ((void**)&val);
+ if (temprid <= 0 || temprid >= READ_ONLY_REPLICA_ID)
+ {
+ sprintf (errormsg,
+ "attribute %s must have a value greater than 0 "
+ "and less than %d: entry %s",
+ attr_replicaId, READ_ONLY_REPLICA_ID,
+ escape_string((char*)slapi_entry_get_dn ((Slapi_Entry*)e),
+ dnescape));
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "_replica_init_from_config: %s\n",
+ errormsg);
+ return -1;
+ }
+ else
+ {
+ r->repl_rid = (ReplicaId)temprid;
+ }
+ }
+ else
+ {
+ sprintf (errormsg, "failed to retrieve required %s attribute from %s",
+ attr_replicaId,
+ escape_string((char*)slapi_entry_get_dn ((Slapi_Entry*)e),
+ dnescape));
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "_replica_init_from_config: %s\n",
+ errormsg);
+ return -1;
+ }
+ }
+
+ attr = NULL;
+ rc = slapi_entry_attr_find(e, attr_state, &attr);
+ gen = csngen_new (r->repl_rid, attr);
+ if (gen == NULL)
+ {
+ sprintf (errormsg, "failed to create csn generator for replica (%s)",
+ escape_string((char*)slapi_entry_get_dn ((Slapi_Entry*)e),
+ dnescape));
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "_replica_init_from_config: %s\n",
+ errormsg);
+ return -1;
+ }
+ r->repl_csngen = object_new((void*)gen, (FNFree)csngen_free);
+
+ /* Hook generator so we can maintain min/max CSN info */
+ r->csn_pl_reg_id = csngen_register_callbacks(gen, assign_csn_callback, r, abort_csn_callback, r);
+
+ /* get replication bind dn */
+ r->updatedn_list = replica_updatedn_list_new(e);
+
+ /* get replica name */
+ val = slapi_entry_attr_get_charptr (e, attr_replicaName);
+ if (val) {
+ r->repl_name = val;
+ }
+ else
+ {
+ rc = slapi_uniqueIDGenerateString (&r->repl_name);
+ if (rc != UID_SUCCESS)
+ {
+ sprintf (errormsg, "failed to assign replica name for replica (%s); "
+ "uuid generator error - %d ",
+ escape_string((char*)slapi_entry_get_dn ((Slapi_Entry*)e), dnescape),
+ rc);
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_replica_init_from_config: %s\n",
+ errormsg);
+ return -1;
+ }
+ else
+ r->new_name = PR_TRUE;
+ }
+
+ /* get the list of referrals */
+ slapi_entry_attr_find( e, attr_replicaReferral, &attr );
+ if(attr!=NULL)
+ {
+ slapi_attr_get_valueset(attr, &r->repl_referral);
+ }
+
+ /*
+ * Set the purge offset (default 7 days). This is the extra
+ * time we allow purgeable CSNs to stick around, in case a
+ * replica regresses. Could also be useful when LCUP happens,
+ * since we don't know about LCUP replicas, and they can just
+ * turn up whenever they want to.
+ */
+ if (slapi_entry_attr_find(e, type_replicaPurgeDelay, &a) == -1)
+ {
+ /* No purge delay provided, so use default */
+ r->repl_purge_delay = 60 * 60 * 24 * 7; /* One week, in seconds */
+ }
+ else
+ {
+ r->repl_purge_delay = slapi_entry_attr_get_uint(e, type_replicaPurgeDelay);
+ }
+
+ if (slapi_entry_attr_find(e, type_replicaTombstonePurgeInterval, &a) == -1)
+ {
+ /* No reap interval provided, so use default */
+ r->tombstone_reap_interval = 3600 * 24; /* One day */
+ }
+ else
+ {
+ r->tombstone_reap_interval = slapi_entry_attr_get_int(e, type_replicaTombstonePurgeInterval);
+ }
+
+ r->tombstone_reap_stop = r->tombstone_reap_active = PR_FALSE;
+
+ return (_replica_check_validity (r));
+}
+
+/* This function updates the entry to contain information generated
+ during replica initialization.
+ Returns 0 if successful and -1 otherwise */
+static int
+_replica_update_entry (Replica *r, Slapi_Entry *e, char *errortext)
+{
+ int rc;
+ Slapi_Mod smod;
+ Slapi_Value *val;
+
+ PR_ASSERT (r);
+
+ /* add attribute that stores state of csn generator */
+ rc = csngen_get_state ((CSNGen*)object_get_data (r->repl_csngen), &smod);
+ if (rc != CSN_SUCCESS)
+ {
+ sprintf (errortext, "failed to get csn generator's state; csn error - %d", rc);
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "_replica_update_entry: %s\n", errortext);
+ return -1;
+ }
+
+ val = slapi_value_new_berval(slapi_mod_get_first_value(&smod));
+
+ rc = slapi_entry_add_value (e, slapi_mod_get_type (&smod), val);
+
+ slapi_value_free(&val);
+ slapi_mod_done (&smod);
+
+ if (rc != 0)
+ {
+ sprintf (errortext, "failed to update replica entry");
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "_replica_update_entry: %s\n", errortext);
+ return -1;
+ }
+
+ /* add attribute that stores replica name */
+ rc = slapi_entry_add_string (e, attr_replicaName, r->repl_name);
+ if (rc != 0)
+ {
+ sprintf (errortext, "failed to update replica entry");
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "_replica_update_entry: %s\n", errortext);
+ return -1;
+ }
+ else
+ r->new_name = PR_FALSE;
+
+ return 0;
+}
+
+/* DN format: cn=replica,cn=\"<root>\",cn=mapping tree,cn=config */
+static char*
+_replica_get_config_dn (const Slapi_DN *root)
+{
+ char *dn;
+ const char *mp_base = slapi_get_mapping_tree_config_root ();
+ int len;
+
+ PR_ASSERT (root);
+
+ len = strlen (REPLICA_RDN) + strlen (slapi_sdn_get_dn (root)) +
+ strlen (mp_base) + 8; /* 8 = , + cn= + \" + \" + , + \0 */
+
+ dn = (char*)slapi_ch_malloc (len);
+ sprintf (dn, "%s,cn=\"%s\",%s", REPLICA_RDN, slapi_sdn_get_dn (root), mp_base);
+
+ return dn;
+}
+
+/* This function retrieves RUV from the root of the replicated tree.
+ * The attribute can be missing if
+ * (1) this replica is the first supplier and replica generation has not been assigned
+ * or
+ * (2) this is a consumer that has not been yet initialized
+ * In either case, replica_set_ruv should be used to further initialize the replica.
+ * Returns 0 on success, -1 on failure. If 0 is returned, the RUV is present in the replica.
+ */
+static int
+_replica_configure_ruv (Replica *r, PRBool isLocked)
+{
+ Slapi_PBlock *pb = NULL;
+ char *attrs[2];
+ int rc;
+ int return_value = -1;
+ Slapi_Entry **entries = NULL;
+ Slapi_Attr *attr;
+ RUV *ruv = NULL;
+ CSN *csn = NULL;
+ ReplicaId rid = 0;
+ char ebuf[BUFSIZ];
+
+ /* read ruv state from the ruv tombstone entry */
+ pb = slapi_pblock_new();
+ attrs[0] = (char*)type_ruvElement;
+ attrs[1] = NULL;
+ slapi_search_internal_set_pb(
+ pb,
+ slapi_sdn_get_dn(r->repl_root),
+ LDAP_SCOPE_BASE,
+ "objectclass=*",
+ attrs,
+ 0, /* attrsonly */
+ NULL, /* controls */
+ RUV_STORAGE_ENTRY_UNIQUEID,
+ repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION),
+ OP_FLAG_REPLICATED); /* flags */
+ slapi_search_internal_pb (pb);
+
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
+ if (rc == LDAP_SUCCESS)
+ {
+ /* get RUV attributes and construct the RUV */
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries);
+ if (NULL == entries || NULL == entries[0])
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "_replica_configure_ruv: replica ruv tombstone entry for "
+ "replica %s not found\n",
+ escape_string(slapi_sdn_get_dn(r->repl_root),ebuf));
+ goto done;
+ }
+
+ rc = slapi_entry_attr_find(entries[0], type_ruvElement, &attr);
+ if (rc != 0) /* ruv attribute is missing - this not allowed */
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "_replica_configure_ruv: replica ruv tombstone entry for "
+ "replica %s does not contain %s\n",
+ escape_string(slapi_sdn_get_dn(r->repl_root),ebuf), type_ruvElement);
+ goto done;
+ }
+
+ /* Check in the tombstone we have retrieved if the local purl is
+ already present:
+ rid == 0: the local purl is not present
+ rid != 0: the local purl is present ==> nothing to do
+ */
+ ruv_init_from_slapi_attr_and_check_purl (attr, &ruv, &rid);
+ if (ruv)
+ {
+ char *generation = NULL;
+ generation = ruv_get_replica_generation(ruv);
+ if (NULL != generation)
+ {
+ r->repl_ruv = object_new((void*)ruv, (FNFree)ruv_destroy);
+
+ /* Is the local purl in the ruv? (the port or the host could have
+ changed)
+ */
+ /* A consumer only doesn't have its purl in its ruv */
+ if (r->repl_type == REPLICA_TYPE_UPDATABLE)
+ {
+ int need_update = 0;
+ if (rid == 0)
+ {
+ /* We can not have more than 1 ruv with the same rid
+ so we replace it */
+ const char *purl = NULL;
+
+ purl = multimaster_get_local_purl();
+ ruv_delete_replica(ruv, r->repl_rid);
+ ruv_add_index_replica(ruv, r->repl_rid, purl, 1);
+ need_update = 1; /* ruv changed, so write tombstone */
+ }
+ else /* bug 540844: make sure the local supplier rid is first in the ruv */
+ {
+ /* make sure local supplier is first in list */
+ ReplicaId first_rid = 0;
+ char *first_purl = NULL;
+ ruv_get_first_id_and_purl(ruv, &first_rid, &first_purl);
+ /* if the local supplier is not first in the list . . . */
+ if (rid != first_rid)
+ {
+ /* . . . move the local supplier to the beginning of the list */
+ ruv_move_local_supplier_to_first(ruv, rid);
+ need_update = 1; /* must update tombstone also */
+ }
+ }
+
+ /* Update also the directory entry */
+ if (need_update) {
+ /* richm 20010821 bug 556498
+ replica_replace_ruv_tombstone acquires the repl_lock, so release
+ the lock then reacquire it if locked */
+ if (isLocked) PR_Unlock(r->repl_lock);
+ replica_replace_ruv_tombstone(r);
+ if (isLocked) PR_Lock(r->repl_lock);
+ }
+ }
+
+ slapi_ch_free((void **)&generation);
+ return_value = 0;
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "RUV for replica %s is missing replica generation\n",
+ escape_string(slapi_sdn_get_dn(r->repl_root),ebuf));
+ goto done;
+ }
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "Unable to convert %s attribute in entry %s to a replica update vector.\n",
+ type_ruvElement, escape_string(slapi_sdn_get_dn(r->repl_root),ebuf));
+ goto done;
+ }
+
+ }
+ else /* search failed */
+ {
+ if (LDAP_NO_SUCH_OBJECT == rc)
+ {
+ /* The entry doesn't exist: create it */
+ rc = replica_create_ruv_tombstone(r);
+ if (LDAP_SUCCESS != rc)
+ {
+ /*
+ * XXXggood - the following error appears on startup if we try
+ * to initialize replica RUVs before the backend instance is up.
+ * It's alarming to see this error, and we should suppress it
+ * (or avoid trying to configure it) if the backend instance is
+ * not yet online.
+ */
+ /*
+ * XXXrichm - you can also get this error when the backend is in
+ * read only mode c.f. bug 539782
+ */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "_replica_configure_ruv: failed to create replica ruv tombstone "
+ "entry (%s); LDAP error - %d\n",
+ escape_string(slapi_sdn_get_dn(r->repl_root),ebuf), rc);
+ goto done;
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "_replica_configure_ruv: No ruv tombstone found for replica %s. "
+ "Created a new one\n",
+ escape_string(slapi_sdn_get_dn(r->repl_root),ebuf));
+ return_value = 0;
+ }
+ }
+ else
+ {
+ /* see if the suffix is disabled */
+ char *state = slapi_mtn_get_state(r->repl_root);
+ if (state && !strcasecmp(state, "disabled"))
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "_replica_configure_ruv: replication disabled for "
+ "entry (%s); LDAP error - %d\n",
+ escape_string(slapi_sdn_get_dn(r->repl_root),ebuf), rc);
+ slapi_ch_free_string(&state);
+ goto done;
+ }
+ else if (!r->repl_ruv) /* other error */
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "_replica_configure_ruv: replication broken for "
+ "entry (%s); LDAP error - %d\n",
+ escape_string(slapi_sdn_get_dn(r->repl_root),ebuf), rc);
+ slapi_ch_free_string(&state);
+ goto done;
+ }
+ else /* some error but continue anyway? */
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "_replica_configure_ruv: Error %d reading tombstone for replica %s.\n",
+ rc, escape_string(slapi_sdn_get_dn(r->repl_root),ebuf));
+ return_value = 0;
+ }
+ slapi_ch_free_string(&state);
+ }
+ }
+
+ if (NULL != r->min_csn_pl)
+ {
+ csnplFree (&r->min_csn_pl);
+ }
+
+ /* create pending list for min csn if necessary */
+ if (ruv_get_smallest_csn_for_replica ((RUV*)object_get_data (r->repl_ruv),
+ r->repl_rid, &csn) == RUV_SUCCESS)
+ {
+ csn_free (&csn);
+ r->min_csn_pl = NULL;
+ }
+ else
+ {
+ /*
+ * The local replica has not generated any of its own CSNs yet.
+ * We need to watch CSNs being generated and note the first
+ * locally-generated CSN that's committed. Once that event occurs,
+ * the RUV is suitable for iteration over locally generated
+ * changes.
+ */
+ r->min_csn_pl = csnplNew();
+ }
+
+done:
+ if (NULL != pb)
+ {
+ slapi_free_search_results_internal(pb);
+ slapi_pblock_destroy (pb);
+ }
+ if (return_value != 0)
+ {
+ if (ruv)
+ ruv_destroy (&ruv);
+ }
+
+ return return_value;
+}
+
+/* NOTE - this is the only non-api function that performs locking because
+ it is called by the event queue */
+static void
+_replica_update_state (time_t when, void *arg)
+{
+ int rc;
+ const char *replica_name = (const char *)arg;
+ Object *replica_object = NULL;
+ Replica *r;
+ Slapi_Mod smod;
+ LDAPMod *mods[3];
+ Slapi_PBlock *pb = NULL;
+ char *dn = NULL;
+
+ if (NULL == replica_name)
+ return;
+
+ /*
+ * replica_get_by_name() will acquire the replica object
+ * and that could prevent the replica from being destroyed
+ * until the object_release is called.
+ */
+ replica_object = replica_get_by_name(replica_name);
+ if (NULL == replica_object)
+ {
+ return;
+ }
+
+ /* We have a reference, so replica won't vanish on us. */
+ r = (Replica *)object_get_data(replica_object);
+ if (NULL == r)
+ {
+ goto done;
+ }
+
+ PR_Lock(r->repl_lock);
+
+ /* replica state is currently being updated
+ or no CSN was assigned - bail out */
+ if (r->state_update_inprogress)
+ {
+ PR_Unlock(r->repl_lock);
+ goto done;
+ }
+
+ /* This might be a consumer */
+ if (!r->repl_csn_assigned)
+ {
+ /* EY: the consumer needs to flush ruv to disk. */
+ PR_Unlock(r->repl_lock);
+ replica_write_ruv(r);
+ goto done;
+ }
+
+ /* ONREPL update csn generator state of an updatable replica only */
+ /* ONREPL state always changes because we update time every second and
+ we write state to the disk less frequently */
+ rc = csngen_get_state ((CSNGen*)object_get_data (r->repl_csngen), &smod);
+ if (rc != 0)
+ {
+ PR_Unlock(r->repl_lock);
+ goto done;
+ }
+
+ r->state_update_inprogress = PR_TRUE;
+ r->repl_csn_assigned = PR_FALSE;
+
+ dn = _replica_get_config_dn (r->repl_root);
+ pb = slapi_pblock_new();
+ mods[0] = (LDAPMod*)slapi_mod_get_ldapmod_byref(&smod);
+
+ /* we don't want to held lock during operations since it causes lock contention
+ and sometimes deadlock. So releasing lock here */
+
+ PR_Unlock(r->repl_lock);
+
+ /* replica repl_name and new_name attributes do not get changed once
+ the replica is configured - so it is ok that they are outside replica lock */
+
+ /* write replica name if it has not been written before */
+ if (r->new_name)
+ {
+ struct berval *vals[2];
+ struct berval val;
+ LDAPMod mod;
+
+ mods[1] = &mod;
+
+ mod.mod_op = LDAP_MOD_REPLACE;
+ mod.mod_type = (char*)attr_replicaName;
+ mod.mod_bvalues = vals;
+ vals [0] = &val;
+ vals [1] = NULL;
+ val.bv_val = r->repl_name;
+ val.bv_len = strlen (val.bv_val);
+ mods[2] = NULL;
+ }
+ else
+ {
+ mods[1] = NULL;
+ }
+
+ slapi_modify_internal_set_pb (pb, dn, mods, NULL, NULL,
+ repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0);
+ slapi_modify_internal_pb (pb);
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
+ if (rc != LDAP_SUCCESS)
+ {
+ char ebuf[BUFSIZ];
+
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_replica_update_state: "
+ "failed to update state of csn generator for replica %s: LDAP "
+ "error - %d\n", escape_string(slapi_sdn_get_dn(r->repl_root),ebuf), rc);
+ }
+ else
+ {
+ r->new_name = PR_FALSE;
+ }
+
+ /* update RUV - performs its own locking */
+ replica_write_ruv (r);
+
+ /* since this is the only place this value is changed and we are
+ guaranteed that only one thread enters the function, its ok
+ to change it outside replica lock */
+ r->state_update_inprogress = PR_FALSE;
+
+ slapi_ch_free ((void**)&dn);
+ slapi_pblock_destroy (pb);
+ slapi_mod_done (&smod);
+
+done:
+ if (replica_object)
+ object_release (replica_object);
+}
+
+void
+replica_write_ruv (Replica *r)
+{
+ int rc;
+ Slapi_Mod smod;
+ Slapi_Mod smod_last_modified;
+ LDAPMod *mods [3];
+ Slapi_PBlock *pb;
+
+ PR_ASSERT(r);
+
+ PR_Lock(r->repl_lock);
+
+ if (!r->repl_ruv_dirty)
+ {
+ PR_Unlock(r->repl_lock);
+ return;
+ }
+
+ PR_ASSERT (r->repl_ruv);
+
+ ruv_to_smod ((RUV*)object_get_data(r->repl_ruv), &smod);
+ ruv_last_modified_to_smod ((RUV*)object_get_data(r->repl_ruv), &smod_last_modified);
+
+ PR_Unlock (r->repl_lock);
+
+ mods [0] = (LDAPMod *)slapi_mod_get_ldapmod_byref(&smod);
+ mods [1] = (LDAPMod *)slapi_mod_get_ldapmod_byref(&smod_last_modified);
+ mods [2] = NULL;
+ pb = slapi_pblock_new();
+
+ /* replica name never changes so it is ok to reference it outside the lock */
+ slapi_modify_internal_set_pb(
+ pb,
+ slapi_sdn_get_dn(r->repl_root), /* only used to select be */
+ mods,
+ NULL, /* controls */
+ RUV_STORAGE_ENTRY_UNIQUEID,
+ repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION),
+ /* Add OP_FLAG_TOMBSTONE_ENTRY so that this doesn't get logged in the Retro ChangeLog */
+ OP_FLAG_REPLICATED | OP_FLAG_REPL_FIXUP | OP_FLAG_TOMBSTONE_ENTRY);
+ slapi_modify_internal_pb (pb);
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
+
+ /* ruv does not exist - create one */
+ PR_Lock(r->repl_lock);
+
+ if (rc == LDAP_SUCCESS)
+ {
+ r->repl_ruv_dirty = PR_FALSE;
+ }
+ else if (rc == LDAP_NO_SUCH_OBJECT)
+ {
+ /* this includes an internal operation - but since this only happens
+ during server startup - its ok that we have lock around it */
+ rc = _replica_configure_ruv (r, PR_TRUE);
+ if (rc == 0)
+ r->repl_ruv_dirty = PR_FALSE;
+ }
+ else /* error */
+ {
+ char ebuf[BUFSIZ];
+
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "replica_write_ruv: failed to update RUV tombstone for %s; "
+ "LDAP error - %d\n",
+ escape_string(slapi_sdn_get_dn(r->repl_root),ebuf), rc);
+ PR_ASSERT (0);
+ }
+
+ PR_Unlock(r->repl_lock);
+
+ slapi_mod_done (&smod);
+ slapi_mod_done (&smod_last_modified);
+ slapi_pblock_destroy (pb);
+}
+
+
+const CSN *
+_get_deletion_csn(Slapi_Entry *e)
+{
+ const CSN *deletion_csn = NULL;
+
+ PR_ASSERT(NULL != e);
+ if (NULL != e)
+ {
+ Slapi_Attr *oc_attr = NULL;
+ if (entry_attr_find_wsi(e, SLAPI_ATTR_OBJECTCLASS, &oc_attr) == ATTRIBUTE_PRESENT)
+ {
+ Slapi_Value *tombstone_value = NULL;
+ struct berval v;
+ v.bv_val = SLAPI_ATTR_VALUE_TOMBSTONE;
+ v.bv_len = strlen(SLAPI_ATTR_VALUE_TOMBSTONE);
+ if (attr_value_find_wsi(oc_attr, &v, &tombstone_value) == VALUE_PRESENT)
+ {
+ deletion_csn = value_get_csn(tombstone_value, CSN_TYPE_VALUE_UPDATED);
+ }
+ }
+ }
+ return deletion_csn;
+}
+
+
+static void
+_delete_tombstone(const char *tombstone_dn, const char *uniqueid)
+{
+
+ PR_ASSERT(NULL != tombstone_dn && NULL != uniqueid);
+ if (NULL == tombstone_dn || NULL == uniqueid)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_delete_tombstone: "
+ "NULL tombstone_dn or uniqueid provided.\n");
+ }
+ else
+ {
+ int ldaprc;
+ Slapi_PBlock *pb = slapi_pblock_new();
+ slapi_delete_internal_set_pb(pb, tombstone_dn, NULL, /* controls */
+ uniqueid, repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION),
+ OP_FLAG_TOMBSTONE_ENTRY);
+ slapi_delete_internal_pb(pb);
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &ldaprc);
+ if (LDAP_SUCCESS != ldaprc)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "_delete_tombstone: unable to delete tombstone %s, "
+ "uniqueid %s: %s.\n", tombstone_dn, uniqueid,
+ ldap_err2string(ldaprc));
+ }
+ slapi_pblock_destroy(pb);
+ }
+}
+
+static
+void get_reap_result (int rc, void *cb_data)
+{
+ PR_ASSERT (cb_data);
+
+ ((reap_callback_data*)cb_data)->rc = rc;
+}
+
+static
+int process_reap_entry (Slapi_Entry *entry, void *cb_data)
+{
+ char ebuf[BUFSIZ];
+ char deletion_csn_str[CSN_STRSIZE];
+ char purge_csn_str[CSN_STRSIZE];
+ unsigned long *num_entriesp = &((reap_callback_data *)cb_data)->num_entries;
+ unsigned long *num_purged_entriesp = &((reap_callback_data *)cb_data)->num_purged_entries;
+ CSN *purge_csn = ((reap_callback_data *)cb_data)->purge_csn;
+ PRBool *tombstone_reap_stop = ((reap_callback_data *)cb_data)->tombstone_reap_stop;
+ /* we only ask for the objectclass in the search - the deletion csn is in the
+ objectclass attribute values - if we need more attributes returned by the
+ search in the future, see _replica_reap_tombstones below and add more to the
+ attrs array */
+ const CSN *deletion_csn = _get_deletion_csn(entry);
+
+ if ((NULL == deletion_csn || csn_compare(deletion_csn, purge_csn) < 0) &&
+ (!is_ruv_tombstone_entry(entry))) {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "_replica_reap_tombstones: removing tombstone %s "
+ "because its deletion csn (%s) is less than the "
+ "purge csn (%s).\n",
+ escape_string(slapi_entry_get_dn(entry), ebuf),
+ csn_as_string(deletion_csn, PR_FALSE, deletion_csn_str),
+ csn_as_string(purge_csn, PR_FALSE, purge_csn_str));
+ _delete_tombstone(slapi_entry_get_dn(entry),
+ slapi_entry_get_uniqueid(entry));
+ (*num_purged_entriesp)++;
+ }
+ else {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "_replica_reap_tombstones: NOT removing tombstone "
+ "%s\n", escape_string(slapi_entry_get_dn(entry),ebuf));
+ }
+ (*num_entriesp)++;
+ if (*tombstone_reap_stop || g_get_shutdown()) {
+ return -1;
+ }
+
+ return 0;
+}
+
+
+
+
+/* This does the actual work of searching for tombstones and deleting them.
+ This must be called in a separate thread because it may take a long time.
+*/
+static void
+_replica_reap_tombstones(void *arg)
+{
+ const char *replica_name = (const char *)arg;
+ Slapi_PBlock *pb = NULL;
+ Object *replica_object = NULL;
+ Replica *replica = NULL;
+ CSN *purge_csn = NULL;
+ char ebuf[BUFSIZ];
+
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "Info: Beginning tombstone reap for replica %s.\n",
+ replica_name ? replica_name : "(null)");
+
+ if (NULL == replica_name)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "Warning: Replica name is null in tombstone reap\n");
+ goto done;
+ }
+
+ /*
+ * replica_get_by_name() will acquire the replica object
+ * and that could prevent the replica from being destroyed
+ * until the object_release is called.
+ */
+ replica_object = replica_get_by_name(replica_name);
+ if (NULL == replica_object)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "Warning: Replica object %s is null in tombstone reap\n", replica_name);
+ goto done;
+ }
+
+ /* We have a reference, so replica won't vanish on us. */
+ replica = (Replica *)object_get_data(replica_object);
+ if (NULL == replica)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "Warning: Replica %s is null in tombstone reap\n", replica_name);
+ goto done;
+ }
+
+ if (replica->tombstone_reap_stop)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "Info: Replica %s reap stop flag is set for tombstone reap\n", replica_name);
+ goto done;
+ }
+
+ purge_csn = replica_get_purge_csn(replica);
+ if (NULL != purge_csn)
+ {
+ LDAPControl **ctrls;
+ int oprc;
+ reap_callback_data cb_data;
+ char **attrs = NULL;
+
+ /* we just need the objectclass - for the deletion csn
+ and the dn and nsuniqueid - for possible deletion
+ saves time to return only 2 attrs
+ */
+ charray_add(&attrs, slapi_ch_strdup("objectclass"));
+ charray_add(&attrs, slapi_ch_strdup("nsuniqueid"));
+
+ ctrls = (LDAPControl **)slapi_ch_calloc (3, sizeof (LDAPControl *));
+ ctrls[0] = create_managedsait_control();
+ ctrls[1] = create_backend_control(replica->repl_root);
+ ctrls[2] = NULL;
+ pb = slapi_pblock_new();
+ slapi_search_internal_set_pb(pb, slapi_sdn_get_dn(replica->repl_root),
+ LDAP_SCOPE_SUBTREE, "(&(objectclass=nstombstone)(nscpentrydn=*))",
+ attrs, 0, ctrls, NULL,
+ repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION), 0);
+
+ cb_data.rc = 0;
+ cb_data.num_entries = 0UL;
+ cb_data.num_purged_entries = 0UL;
+ cb_data.purge_csn = purge_csn;
+ cb_data.tombstone_reap_stop = &(replica->tombstone_reap_stop);
+
+ slapi_search_internal_callback_pb (pb, &cb_data /* callback data */,
+ get_reap_result /* result callback */,
+ process_reap_entry /* entry callback */,
+ NULL /* referral callback*/);
+
+ charray_free(attrs);
+
+ oprc = cb_data.rc;
+
+ if (LDAP_SUCCESS != oprc)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "_replica_reap_tombstones: failed when searching for "
+ "tombstones in replica %s: %s. Will try again in %d "
+ "seconds.\n", escape_string(slapi_sdn_get_dn(replica->repl_root),ebuf),
+ ldap_err2string(oprc), replica->tombstone_reap_interval);
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "_replica_reap_tombstones: purged %d of %d tombstones "
+ "in replica %s. Will try again in %d "
+ "seconds.\n", cb_data.num_purged_entries, cb_data.num_entries,
+ escape_string(slapi_sdn_get_dn(replica->repl_root),ebuf),
+ replica->tombstone_reap_interval);
+ }
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "Info: No purge CSN for tombstone reap for replica %s.\n",
+ replica_name ? replica_name : "(null)");
+ }
+
+ PR_Lock(replica->repl_lock);
+ replica->tombstone_reap_active = PR_FALSE;
+ PR_Unlock(replica->repl_lock);
+
+done:
+ if (NULL != purge_csn)
+ {
+ csn_free(&purge_csn);
+ }
+ if (NULL != pb)
+ {
+ slapi_free_search_results_internal(pb);
+ slapi_pblock_destroy(pb);
+ }
+ if (NULL != replica_object)
+ {
+ object_release(replica_object);
+ replica_object = NULL;
+ replica = NULL;
+ }
+
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "Info: Finished tombstone reap for replica %s.\n",
+ replica_name ? replica_name : "(null)");
+
+}
+
+/*
+ We don't want to run the reaper function directly from the event
+ queue since it may hog the event queue, starving other events.
+ See bug 604441
+ The function eq_cb_reap_tombstones will fire off the actual thread
+ that does the real work.
+*/
+static void
+eq_cb_reap_tombstones(time_t when, void *arg)
+{
+ const char *replica_name = (const char *)arg;
+ Object *replica_object = NULL;
+ Replica *replica = NULL;
+
+ if (NULL != replica_name)
+ {
+ /*
+ * replica_get_by_name() will acquire the replica object
+ * and that could prevent the replica from being destroyed
+ * until the object_release is called.
+ */
+ replica_object = replica_get_by_name(replica_name);
+ if (NULL != replica_object)
+ {
+ /* We have a reference, so replica won't vanish on us. */
+ replica = (Replica *)object_get_data(replica_object);
+ if (replica)
+ {
+
+ PR_Lock(replica->repl_lock);
+
+ /* No action if purge is disabled or the previous purge is not done yet */
+ if (replica->tombstone_reap_interval != 0 &&
+ replica->tombstone_reap_active == PR_FALSE)
+ {
+ /* set the flag here to minimize race conditions */
+ replica->tombstone_reap_active = PR_TRUE;
+ if (PR_CreateThread(PR_USER_THREAD,
+ _replica_reap_tombstones, (void *)replica_name,
+ PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_UNJOINABLE_THREAD,
+ SLAPD_DEFAULT_THREAD_STACKSIZE) == NULL)
+ {
+ replica->tombstone_reap_active = PR_FALSE;
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "Error: unable to create the tombstone reap thread for replica %s. "
+ "Possible system resources problem\n",
+ replica_name);
+ }
+ }
+ /* reap thread will wait until this lock is released */
+ PR_Unlock(replica->repl_lock);
+ }
+ object_release(replica_object);
+ replica_object = NULL;
+ replica = NULL;
+ }
+ }
+}
+
+static char *
+_replica_type_as_string (const Replica *r)
+{
+ switch (r->repl_type)
+ {
+ case REPLICA_TYPE_PRIMARY: return "primary";
+ case REPLICA_TYPE_READONLY: return "read-only";
+ case REPLICA_TYPE_UPDATABLE: return "updatable";
+ default: return "unknown";
+ }
+}
+
+
+static const char *root_glue =
+ "dn: %s\n"
+ "objectclass: top\n"
+ "objectclass: nsTombstone\n"
+ "objectclass: extensibleobject\n"
+ "nsuniqueid: %s\n";
+
+static int
+replica_create_ruv_tombstone(Replica *r)
+{
+ int return_value = LDAP_LOCAL_ERROR;
+ char *root_entry_str;
+ Slapi_Entry *e;
+ const char *purl = NULL;
+ RUV *ruv;
+ struct berval **bvals = NULL;
+ Slapi_PBlock *pb = NULL;
+ int rc;
+ char ebuf[BUFSIZ];
+
+ PR_ASSERT(NULL != r && NULL != r->repl_root);
+ root_entry_str = slapi_ch_malloc(strlen(root_glue) +
+ slapi_sdn_get_ndn_len(r->repl_root) +
+ strlen(RUV_STORAGE_ENTRY_UNIQUEID) + 1);
+ sprintf(root_entry_str, root_glue, slapi_sdn_get_ndn(r->repl_root),
+ RUV_STORAGE_ENTRY_UNIQUEID);
+
+ e = slapi_str2entry(root_entry_str, SLAPI_STR2ENTRY_TOMBSTONE_CHECK);
+ if (e == NULL)
+ goto done;
+
+ /* Add ruv */
+ if (r->repl_ruv == NULL)
+ {
+ CSNGen *gen;
+ CSN *csn;
+ char csnstr [CSN_STRSIZE];
+
+ /* first attempt to write RUV tombstone - need to create RUV */
+ gen = (CSNGen *)object_get_data(r->repl_csngen);
+ PR_ASSERT (gen);
+
+ if (csngen_new_csn(gen, &csn, PR_FALSE /* notify */) == CSN_SUCCESS)
+ {
+ (void)csn_as_string(csn, PR_FALSE, csnstr);
+ csn_free(&csn);
+
+ /* if this is an updateable replica - add its own
+ element to the RUV so that referrals work correctly */
+ if (r->repl_type == REPLICA_TYPE_UPDATABLE)
+ purl = multimaster_get_local_purl();
+
+ if (ruv_init_new(csnstr, r->repl_rid, purl, &ruv) == RUV_SUCCESS)
+ {
+ r->repl_ruv = object_new((void*)ruv, (FNFree)ruv_destroy);
+ r->repl_ruv_dirty = PR_TRUE;
+ return_value = LDAP_SUCCESS;
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "Cannot create new replica update vector for %s\n",
+ escape_string(slapi_sdn_get_dn(r->repl_root),ebuf));
+ goto done;
+ }
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "Cannot obtain CSN for new replica update vector for %s\n",
+ escape_string(slapi_sdn_get_dn(r->repl_root),ebuf));
+ goto done;
+ }
+ }
+ else /* failed to write the entry because DB was not initialized - retry */
+ {
+ ruv = (RUV*) object_get_data (r->repl_ruv);
+ PR_ASSERT (ruv);
+ }
+
+ PR_ASSERT (r->repl_ruv);
+
+ rc = ruv_to_bervals(ruv, &bvals);
+ if (rc != RUV_SUCCESS)
+ {
+ goto done;
+ }
+
+ /* ONREPL this is depricated function but there is currently no better API to use */
+ rc = slapi_entry_add_values(e, type_ruvElement, bvals);
+ if (rc != 0)
+ {
+ goto done;
+ }
+
+
+ pb = slapi_pblock_new();
+ slapi_add_entry_internal_set_pb(
+ pb,
+ e,
+ NULL /* controls */,
+ repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION),
+ OP_FLAG_TOMBSTONE_ENTRY | OP_FLAG_REPLICATED | OP_FLAG_REPL_FIXUP);
+ slapi_add_internal_pb(pb);
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &return_value);
+ if (return_value == LDAP_SUCCESS)
+ r->repl_ruv_dirty = PR_FALSE;
+
+done:
+ if (return_value != LDAP_SUCCESS)
+ {
+ slapi_entry_free (e);
+ }
+
+ if (bvals)
+ ber_bvecfree(bvals);
+
+ if (pb)
+ slapi_pblock_destroy(pb);
+
+ slapi_ch_free((void **) &root_entry_str);
+
+ return return_value;
+}
+
+
+static void
+assign_csn_callback(const CSN *csn, void *data)
+{
+ Replica *r = (Replica *)data;
+ Object *ruv_obj;
+ RUV *ruv;
+
+ PR_ASSERT(NULL != csn);
+ PR_ASSERT(NULL != r);
+
+ ruv_obj = replica_get_ruv (r);
+ PR_ASSERT (ruv_obj);
+ ruv = (RUV*)object_get_data (ruv_obj);
+ PR_ASSERT (ruv);
+
+ PR_Lock(r->repl_lock);
+
+ r->repl_csn_assigned = PR_TRUE;
+
+ if (NULL != r->min_csn_pl)
+ {
+ if (csnplInsert(r->min_csn_pl, csn) != 0)
+ {
+ char ebuf[BUFSIZ];
+ char csn_str[CSN_STRSIZE]; /* For logging only */
+ /* Ack, we can't keep track of min csn. Punt. */
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "assign_csn_callback: "
+ "failed to insert csn %s for replica %s\n",
+ csn_as_string(csn, PR_FALSE, csn_str),
+ escape_string(slapi_sdn_get_dn(r->repl_root), ebuf));
+ csnplFree(&r->min_csn_pl);
+ }
+ }
+
+ ruv_add_csn_inprogress (ruv, csn);
+
+ PR_Unlock(r->repl_lock);
+
+ object_release (ruv_obj);
+}
+
+
+static void
+abort_csn_callback(const CSN *csn, void *data)
+{
+ Replica *r = (Replica *)data;
+ Object *ruv_obj;
+ RUV *ruv;
+ int rc;
+
+ PR_ASSERT(NULL != csn);
+ PR_ASSERT(NULL != data);
+
+ ruv_obj = replica_get_ruv (r);
+ PR_ASSERT (ruv_obj);
+ ruv = (RUV*)object_get_data (ruv_obj);
+ PR_ASSERT (ruv);
+
+ PR_Lock(r->repl_lock);
+
+ if (NULL != r->min_csn_pl)
+ {
+ rc = csnplRemove(r->min_csn_pl, csn);
+ PR_ASSERT(rc == 0);
+ }
+
+ ruv_cancel_csn_inprogress (ruv, csn);
+ PR_Unlock(r->repl_lock);
+
+ object_release (ruv_obj);
+}
+
+static CSN *
+_replica_get_purge_csn_nolock(const Replica *r)
+{
+ static unsigned long a_week = 3600*24*7;
+ CSN *purge_csn = NULL;
+ CSN **csns = NULL;
+ RUV *ruv;
+ time_t cutoff_time;
+ time_t max_time_in_csn_list;
+ int i;
+
+ if (r->repl_purge_delay > 0)
+ {
+ /*
+ * Don't let inactive or obsolete masters in the ruv hold back
+ * the purge forever:
+ * - set a graceful period of at least 7 days;
+ * - set cutoff_time = max(maxcsns) - gracefule_period;
+ * - the first maxcsn that was generated at or after the cutoff
+ * time would be the purge csn.
+ */
+
+ /* get a sorted list of all maxcsns in ruv in ascend order */
+ object_acquire(r->repl_ruv);
+ ruv = object_get_data(r->repl_ruv);
+ csns = cl5BuildCSNList (ruv, NULL);
+ object_release(r->repl_ruv);
+
+ if (csns == NULL)
+ return NULL;
+
+ /* locate the max csn in the csn list */
+ for (i = 0; csns[i]; i++);
+ max_time_in_csn_list = csn_get_time (csns[i-1]);
+
+ if ( r->repl_purge_delay > a_week )
+ {
+ cutoff_time = max_time_in_csn_list - r->repl_purge_delay;
+ }
+ else
+ {
+ cutoff_time = max_time_in_csn_list - a_week;
+ }
+ for (i = 0; csns[i]; i++)
+ {
+ if ( csn_get_time (csns[i]) >= cutoff_time )
+ {
+ purge_csn = csn_dup (csns[i]);
+ break;
+ }
+ }
+
+ /* Subtract purge delay */
+ if (purge_csn)
+ {
+ csn_set_time(purge_csn, csn_get_time(purge_csn) - r->repl_purge_delay);
+ }
+ }
+
+ if (csns)
+ cl5DestroyCSNList (&csns);
+
+ return purge_csn;
+}
+
+static void
+replica_get_referrals_nolock (const Replica *r, char ***referrals)
+{
+ if(referrals!=NULL)
+ {
+
+ int hint;
+ int i= 0;
+ Slapi_Value *v= NULL;
+
+ if (NULL == r->repl_referral)
+ {
+ *referrals = NULL;
+ }
+ else
+ {
+ /* richm: +1 for trailing NULL */
+ *referrals= (char**)slapi_ch_calloc(sizeof(char*),1+slapi_valueset_count(r->repl_referral));
+ hint= slapi_valueset_first_value( r->repl_referral, &v );
+ while(v!=NULL)
+ {
+ const char *s= slapi_value_get_string(v);
+ if(s!=NULL && s[0]!='\0')
+ {
+ (*referrals)[i]= slapi_ch_strdup(s);
+ i++;
+ }
+ hint= slapi_valueset_next_value( r->repl_referral, hint, &v);
+ }
+ (*referrals)[i] = NULL;
+ }
+
+ }
+}
+
+static void
+replica_clear_legacy_referrals(const Slapi_DN *repl_root_sdn,
+ char **referrals, const char *state)
+{
+ repl_set_mtn_state_and_referrals(repl_root_sdn, state, NULL, NULL, referrals);
+}
+
+static void
+replica_remove_legacy_attr (const Slapi_DN *repl_root_sdn, const char *attr)
+{
+ Slapi_PBlock *pb;
+ Slapi_Mods smods;
+ LDAPControl **ctrls;
+ int rc;
+
+ pb = slapi_pblock_new ();
+
+ slapi_mods_init(&smods, 1);
+ slapi_mods_add(&smods, LDAP_MOD_DELETE, attr, 0, NULL);
+
+
+ ctrls = (LDAPControl**)slapi_ch_malloc (2 * sizeof (LDAPControl*));
+ ctrls[0] = create_managedsait_control ();
+ ctrls[1] = NULL;
+
+ /* remove copiedFrom/copyingFrom first */
+ slapi_modify_internal_set_pb (pb, slapi_sdn_get_dn (repl_root_sdn),
+ slapi_mods_get_ldapmods_passout (&smods), ctrls,
+ NULL /*uniqueid */,
+ repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION) ,
+ 0 /* operation_flags */);
+
+ slapi_modify_internal_pb (pb);
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
+ if (rc != LDAP_SUCCESS)
+ {
+ char ebuf[BUFSIZ];
+
+ /* this is not a fatal error because the attribute may not be there */
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "replica_remove_legacy_attr: "
+ "failed to remove legacy attribute %s for replica %s; LDAP error - %d\n",
+ attr, escape_string(slapi_sdn_get_dn(repl_root_sdn),ebuf), rc);
+ }
+
+ slapi_mods_done (&smods);
+ slapi_pblock_destroy (pb);
+}
+
+static int
+replica_log_ruv_elements_nolock (const Replica *r)
+{
+ int rc = 0;
+ slapi_operation_parameters op_params;
+ RUV *ruv;
+ char *repl_gen;
+ CSN *csn = NULL;
+
+ ruv = (RUV*) object_get_data (r->repl_ruv);
+ PR_ASSERT (ruv);
+
+ if ((ruv_get_min_csn(ruv, &csn) == RUV_SUCCESS) && csn)
+ {
+ /* we log it as a delete operation to have the least number of fields
+ to set. the entry can be identified by a special target uniqueid and
+ special target dn */
+ memset (&op_params, 0, sizeof (op_params));
+ op_params.operation_type = SLAPI_OPERATION_DELETE;
+ op_params.target_address.dn = START_ITERATION_ENTRY_DN;
+ op_params.target_address.uniqueid = START_ITERATION_ENTRY_UNIQUEID;
+ op_params.csn = csn;
+ repl_gen = ruv_get_replica_generation (ruv);
+
+ rc = cl5WriteOperation(r->repl_name, repl_gen, &op_params, PR_FALSE);
+ if (rc == CL5_SUCCESS)
+ rc = 0;
+ else
+ rc = -1;
+
+ slapi_ch_free ((void**)&repl_gen);
+ csn_free (&csn);
+ }
+
+ return rc;
+}
+
+void
+replica_set_purge_delay(Replica *r, PRUint32 purge_delay)
+{
+ PR_ASSERT(r);
+ PR_Lock(r->repl_lock);
+ r->repl_purge_delay = purge_delay;
+ PR_Unlock(r->repl_lock);
+}
+
+void
+replica_set_tombstone_reap_interval (Replica *r, long interval)
+{
+ char *repl_name;
+
+ PR_Lock(r->repl_lock);
+
+ /*
+ * Leave the event there to purge the existing tombstones
+ * if we are about to turn off tombstone creation
+ */
+ if (interval > 0 && r->repl_eqcxt_tr && r->tombstone_reap_interval != interval)
+ {
+ int found;
+
+ repl_name = slapi_eq_get_arg (r->repl_eqcxt_tr);
+ slapi_ch_free ((void**)&repl_name);
+ found = slapi_eq_cancel (r->repl_eqcxt_tr);
+ slapi_log_error (SLAPI_LOG_REPL, NULL,
+ "tombstone_reap event (interval=%d) was %s\n",
+ r->tombstone_reap_interval, (found ? "cancelled" : "not found"));
+ r->repl_eqcxt_tr = NULL;
+ }
+ r->tombstone_reap_interval = interval;
+ if ( interval > 0 && r->repl_eqcxt_tr == NULL )
+ {
+ repl_name = slapi_ch_strdup (r->repl_name);
+ r->repl_eqcxt_tr = slapi_eq_repeat (eq_cb_reap_tombstones, repl_name, current_time() + START_REAP_DELAY, 1000 * r->tombstone_reap_interval);
+ slapi_log_error (SLAPI_LOG_REPL, NULL,
+ "tombstone_reap event (interval=%d) was %s\n",
+ r->tombstone_reap_interval, (r->repl_eqcxt_tr ? "scheduled" : "not scheduled successfully"));
+ }
+ PR_Unlock(r->repl_lock);
+}
+
+/* Update the tombstone entry to reflect the content of the ruv */
+static void
+replica_replace_ruv_tombstone(Replica *r)
+{
+ Slapi_PBlock *pb = NULL;
+ char *dn;
+ int rc;
+
+ Slapi_Mod smod;
+ Slapi_Mod smod_last_modified;
+ LDAPMod *mods [3];
+
+ PR_ASSERT(NULL != r && NULL != r->repl_root);
+
+ PR_Lock(r->repl_lock);
+
+ PR_ASSERT (r->repl_ruv);
+ ruv_to_smod ((RUV*)object_get_data(r->repl_ruv), &smod);
+ ruv_last_modified_to_smod ((RUV*)object_get_data(r->repl_ruv), &smod_last_modified);
+
+ dn = _replica_get_config_dn (r->repl_root);
+ mods[0] = (LDAPMod*)slapi_mod_get_ldapmod_byref(&smod);
+ mods[1] = (LDAPMod*)slapi_mod_get_ldapmod_byref(&smod_last_modified);
+
+ PR_Unlock (r->repl_lock);
+
+ mods [2] = NULL;
+ pb = slapi_pblock_new();
+
+ slapi_modify_internal_set_pb(
+ pb,
+ (char*)slapi_sdn_get_dn (r->repl_root), /* only used to select be */
+ mods,
+ NULL, /* controls */
+ RUV_STORAGE_ENTRY_UNIQUEID,
+ repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION),
+ OP_FLAG_REPLICATED | OP_FLAG_REPL_FIXUP);
+
+ slapi_modify_internal_pb (pb);
+
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
+
+ if (rc != LDAP_SUCCESS)
+ {
+ if ((rc != LDAP_NO_SUCH_OBJECT) || !replica_is_state_flag_set(r, REPLICA_IN_USE))
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_replace_ruv_tombstone: "
+ "failed to update replication update vector for replica %s: LDAP "
+ "error - %d\n", (char*)slapi_sdn_get_dn (r->repl_root), rc);
+ }
+ }
+
+ slapi_ch_free ((void**)&dn);
+ slapi_pblock_destroy (pb);
+ slapi_mod_done (&smod);
+ slapi_mod_done (&smod_last_modified);
+}
+
+void
+replica_update_ruv_consumer(Replica *r, RUV *supplier_ruv)
+{
+ ReplicaId supplier_id = 0;
+ char *supplier_purl = NULL;
+
+ if ( ruv_get_first_id_and_purl(supplier_ruv, &supplier_id, &supplier_purl) == RUV_SUCCESS )
+ {
+ RUV *local_ruv = NULL;
+
+ PR_Lock(r->repl_lock);
+
+ local_ruv = (RUV*)object_get_data (r->repl_ruv);
+ PR_ASSERT (local_ruv);
+
+ if ( ruv_local_contains_supplier(local_ruv, supplier_id) == 0 )
+ {
+ if ( r->repl_type == REPLICA_TYPE_UPDATABLE )
+ {
+ /* Add the new ruv right after the consumer own purl */
+ ruv_add_index_replica(local_ruv, supplier_id, supplier_purl, 2);
+ }
+ else
+ {
+ /* This is a consumer only, add it first */
+ ruv_add_index_replica(local_ruv, supplier_id, supplier_purl, 1);
+ }
+ }
+ else
+ {
+ /* Replace it */
+ ruv_replace_replica_purl(local_ruv, supplier_id, supplier_purl);
+ }
+ PR_Unlock(r->repl_lock);
+
+ /* Update also the directory entry */
+ replica_replace_ruv_tombstone(r);
+ }
+}
+
+void
+replica_set_ruv_dirty(Replica *r)
+{
+ PR_ASSERT(r);
+ PR_Lock(r->repl_lock);
+ r->repl_ruv_dirty = PR_TRUE;
+ PR_Unlock(r->repl_lock);
+}
+
+PRBool
+replica_is_state_flag_set(Replica *r, PRInt32 flag)
+{
+ PR_ASSERT(r);
+ if (r)
+ return (r->repl_state_flags & flag);
+ else
+ return PR_FALSE;
+}
+
+void
+replica_set_state_flag (Replica *r, PRUint32 flag, PRBool clear)
+{
+ if (r == NULL)
+ return;
+
+ PR_Lock(r->repl_lock);
+
+ if (clear)
+ {
+ r->repl_state_flags &= ~flag;
+ }
+ else
+ {
+ r->repl_state_flags |= flag;
+ }
+
+ PR_Unlock(r->repl_lock);
+}
+
+/* replica just came back online, probably after data was reloaded */
+void
+replica_enable_replication (Replica *r)
+{
+ int rc;
+
+ PR_ASSERT(r);
+
+ /* prevent creation of new agreements until the replica is enabled */
+ PR_Lock(r->agmt_lock);
+
+ /* retrieve new ruv */
+ rc = replica_reload_ruv (r);
+ if (rc) {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_enable_replication: "
+ "reloading ruv failed\n");
+ /* What to do ? */
+ }
+
+ /* Replica came back online, Check if the total update was terminated.
+ If flag is still set, it was not terminated, therefore the data is
+ very likely to be incorrect, and we should not restart Replication threads...
+ */
+ if (!replica_is_state_flag_set(r, REPLICA_TOTAL_IN_PROGRESS)){
+ /* restart outbound replication */
+ start_agreements_for_replica (r, PR_TRUE);
+
+ /* enable ruv state update */
+ replica_set_enabled (r, PR_TRUE);
+ }
+
+ /* mark the replica as being available for updates */
+ replica_relinquish_exclusive_access(r, 0, 0);
+
+ replica_set_state_flag(r, REPLICA_AGREEMENTS_DISABLED, PR_TRUE /* clear */);
+ PR_Unlock(r->agmt_lock);
+
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "replica_enable_replication: "
+ "replica %s is relinquished\n",
+ slapi_sdn_get_ndn (replica_get_root (r)));
+}
+
+/* replica is about to be taken offline */
+void
+replica_disable_replication (Replica *r, Object *r_obj)
+{
+ char *current_purl = NULL;
+ char *p_locking_purl = NULL;
+ char *locking_purl = NULL;
+ int junkrc;
+ ReplicaId junkrid;
+ PRBool isInc = PR_FALSE; /* get exclusive access, but not for inc update */
+ RUV *repl_ruv = NULL;
+
+ /* prevent creation of new agreements until the replica is disabled */
+ PR_Lock(r->agmt_lock);
+
+ /* stop ruv update */
+ replica_set_enabled (r, PR_FALSE);
+
+ /* disable outbound replication */
+ start_agreements_for_replica (r, PR_FALSE);
+
+ /* close the corresponding changelog file */
+ /* close_changelog_for_replica (r_obj); */
+
+ /* mark the replica as being unavailable for updates */
+ /* If an incremental update is in progress, we want to wait until it is
+ finished until we get exclusive access to the replica, because we have
+ to make sure no operations are in progress - it messes up replication
+ when a restore is in progress but we are still adding replicated entries
+ from a supplier
+ */
+ repl_ruv = (RUV*) object_get_data (r->repl_ruv);
+ junkrc = ruv_get_first_id_and_purl(repl_ruv, &junkrid, &p_locking_purl);
+ locking_purl = slapi_ch_strdup(p_locking_purl);
+ p_locking_purl = NULL;
+ repl_ruv = NULL;
+ while (!replica_get_exclusive_access(r, &isInc, 0, 0, "replica_disable_replication",
+ &current_purl)) {
+ if (!isInc) /* already locked, but not by inc update - break */
+ break;
+ isInc = PR_FALSE;
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "replica_disable_replication: "
+ "replica %s is already locked by (%s) for incoming "
+ "incremental update; sleeping 100ms\n",
+ slapi_sdn_get_ndn (replica_get_root (r)),
+ current_purl ? current_purl : "unknown");
+ slapi_ch_free_string(&current_purl);
+ DS_Sleep(PR_MillisecondsToInterval(100));
+ }
+
+ slapi_ch_free_string(&current_purl);
+ slapi_ch_free_string(&locking_purl);
+ replica_set_state_flag(r, REPLICA_AGREEMENTS_DISABLED, PR_FALSE);
+ PR_Unlock(r->agmt_lock);
+
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "replica_disable_replication: "
+ "replica %s is acquired\n",
+ slapi_sdn_get_ndn (replica_get_root (r)));
+}
+
+static void
+start_agreements_for_replica (Replica *r, PRBool start)
+{
+ Object *agmt_obj;
+ Repl_Agmt *agmt;
+
+ agmt_obj = agmtlist_get_first_agreement_for_replica (r);
+ while (agmt_obj)
+ {
+ agmt = (Repl_Agmt*)object_get_data (agmt_obj);
+ PR_ASSERT (agmt);
+
+ if (start)
+ agmt_start (agmt);
+ else /* stop */
+ agmt_stop (agmt);
+
+ agmt_obj = agmtlist_get_next_agreement_for_replica (r, agmt_obj);
+ }
+}
+
+int replica_start_agreement(Replica *r, Repl_Agmt *ra)
+{
+ int ret = 0;
+
+ if (r == NULL) return -1;
+
+ PR_Lock(r->agmt_lock);
+
+ if (!replica_is_state_flag_set(r, REPLICA_AGREEMENTS_DISABLED)) {
+ ret = agmt_start(ra); /* Start the replication agreement */
+ }
+
+ PR_Unlock(r->agmt_lock);
+ return ret;
+}
+
+/*
+ * A callback function registed as op->o_csngen_handler and
+ * called by backend ops to generate opcsn.
+ */
+CSN *
+replica_generate_next_csn ( Slapi_PBlock *pb, const CSN *basecsn )
+{
+ CSN *opcsn = NULL;
+ Object *replica_obj;
+
+ replica_obj = replica_get_replica_for_op (pb);
+ if (NULL != replica_obj)
+ {
+ Replica *replica = (Replica*) object_get_data (replica_obj);
+ if ( NULL != replica )
+ {
+ Slapi_Operation *op;
+ slapi_pblock_get (pb, SLAPI_OPERATION, &op);
+ if ( replica->repl_type != REPLICA_TYPE_READONLY ||
+ operation_is_flag_set (op, OP_FLAG_LEGACY_REPLICATION_DN ))
+ {
+ Object *gen_obj = replica_get_csngen (replica);
+ if (NULL != gen_obj)
+ {
+ CSNGen *gen = (CSNGen*) object_get_data (gen_obj);
+ if (NULL != gen)
+ {
+ /* The new CSN should be greater than the base CSN */
+ csngen_new_csn (gen, &opcsn, PR_FALSE /* don't notify */);
+ if (csn_compare (opcsn, basecsn) <= 0)
+ {
+ char opcsnstr[CSN_STRSIZE], basecsnstr[CSN_STRSIZE];
+ char opcsn2str[CSN_STRSIZE];
+
+ csn_as_string (opcsn, PR_FALSE, opcsnstr);
+ csn_as_string (basecsn, PR_FALSE, basecsnstr);
+ csn_free ( &opcsn );
+ csngen_adjust_time (gen, basecsn);
+ csngen_new_csn (gen, &opcsn, PR_FALSE /* don't notify */);
+ csn_as_string (opcsn, PR_FALSE, opcsn2str);
+ slapi_log_error (SLAPI_LOG_FATAL, NULL,
+ "replica_generate_next_csn: "
+ "opcsn=%s <= basecsn=%s, adjusted opcsn=%s\n",
+ opcsnstr, basecsnstr, opcsn2str);
+ }
+ /*
+ * Insert opcsn into the csn pending list.
+ * This is the notify effect in csngen_new_csn().
+ */
+ assign_csn_callback (opcsn, (void *)replica);
+ }
+ object_release (gen_obj);
+ }
+ }
+ }
+ object_release (replica_obj);
+ }
+
+ return opcsn;
+}
+
+/*
+ * A callback function registed as op->o_replica_attr_handler and
+ * called by backend ops to get replica attributes.
+ */
+int
+replica_get_attr ( Slapi_PBlock *pb, const char* type, void *value )
+{
+ int rc = -1;
+
+ Object *replica_obj;
+ replica_obj = replica_get_replica_for_op (pb);
+ if (NULL != replica_obj)
+ {
+ Replica *replica = (Replica*) object_get_data (replica_obj);
+ if ( NULL != replica )
+ {
+ if (strcasecmp (type, type_replicaTombstonePurgeInterval) == 0)
+ {
+ *((int*)value) = replica->tombstone_reap_interval;
+ rc = 0;
+ }
+ else if (strcasecmp (type, type_replicaPurgeDelay) == 0)
+ {
+ *((int*)value) = replica->repl_purge_delay;
+ rc = 0;
+ }
+ }
+ object_release (replica_obj);
+ }
+
+ return rc;
+}
diff --git a/ldap/servers/plugins/replication/repl5_replica_config.c b/ldap/servers/plugins/replication/repl5_replica_config.c
new file mode 100644
index 00000000..df2573a0
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl5_replica_config.c
@@ -0,0 +1,750 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+/* repl5_replica_config.c - replica configuration over ldap */
+#include <ctype.h> /* for isdigit() */
+#include "repl.h" /* ONREPL - this is bad */
+#include "repl5.h"
+#include "cl5_api.h"
+
+#define CONFIG_BASE "cn=mapping tree,cn=config"
+#define CONFIG_FILTER "(objectclass=nsDS5Replica)"
+#define TASK_ATTR "nsds5Task"
+#define CL2LDIF_TASK "CL2LDIF"
+#define CLEANRUV "CLEANRUV"
+#define CLEANRUVLEN 8
+
+int slapi_log_urp = SLAPI_LOG_REPL;
+
+/* Forward Declartions */
+static int replica_config_add (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg);
+static int replica_config_modify (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg);
+static int replica_config_delete (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg);
+static int replica_config_search (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg);
+
+static int replica_config_change_type_and_id (Replica *r, const char *new_type, const char *new_id, char *returntext, int apply_mods);
+static int replica_config_change_updatedn (Replica *r, const LDAPMod *mod, char *returntext, int apply_mods);
+static int replica_config_change_flags (Replica *r, const char *new_flags, char *returntext, int apply_mods);
+static int replica_execute_task (Object *r, const char *task_name, char *returntext, int apply_mods);
+static int replica_execute_cl2ldif_task (Object *r, char *returntext);
+static int replica_execute_cleanruv_task (Object *r, ReplicaId rid, char *returntext);
+
+static multimaster_mtnode_extension * _replica_config_get_mtnode_ext (const Slapi_Entry *e);
+
+static PRLock *s_configLock;
+
+static int
+dont_allow_that(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, int *returncode, char *returntext, void *arg)
+{
+ *returncode = LDAP_UNWILLING_TO_PERFORM;
+ return SLAPI_DSE_CALLBACK_ERROR;
+}
+
+int
+replica_config_init()
+{
+ s_configLock = PR_NewLock ();
+ if (s_configLock == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_config_init: "
+ "failed to cretate configuration lock; NSPR error - %d\n",
+ PR_GetError ());
+ return -1;
+ }
+
+ /* config DSE must be initialized before we get here */
+ slapi_config_register_callback(SLAPI_OPERATION_ADD, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_SUBTREE,
+ CONFIG_FILTER, replica_config_add, NULL);
+ slapi_config_register_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_SUBTREE,
+ CONFIG_FILTER, replica_config_modify,NULL);
+ slapi_config_register_callback(SLAPI_OPERATION_MODRDN, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_SUBTREE,
+ CONFIG_FILTER, dont_allow_that, NULL);
+ slapi_config_register_callback(SLAPI_OPERATION_DELETE, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_SUBTREE,
+ CONFIG_FILTER, replica_config_delete,NULL);
+ slapi_config_register_callback(SLAPI_OPERATION_SEARCH, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_SUBTREE,
+ CONFIG_FILTER, replica_config_search,NULL);
+ return 0;
+}
+
+void
+replica_config_destroy ()
+{
+ if (s_configLock)
+ {
+ PR_DestroyLock (s_configLock);
+ s_configLock = NULL;
+ }
+
+ /* config DSE must be initialized before we get here */
+ slapi_config_remove_callback(SLAPI_OPERATION_ADD, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_SUBTREE,
+ CONFIG_FILTER, replica_config_add);
+ slapi_config_remove_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_SUBTREE,
+ CONFIG_FILTER, replica_config_modify);
+ slapi_config_remove_callback(SLAPI_OPERATION_MODRDN, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_SUBTREE,
+ CONFIG_FILTER, dont_allow_that);
+ slapi_config_remove_callback(SLAPI_OPERATION_DELETE, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_SUBTREE,
+ CONFIG_FILTER, replica_config_delete);
+ slapi_config_remove_callback(SLAPI_OPERATION_SEARCH, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_SUBTREE,
+ CONFIG_FILTER, replica_config_search);
+}
+
+static int
+replica_config_add (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter,
+ int *returncode, char *errorbuf, void *arg)
+{
+ Replica *r = NULL;
+ multimaster_mtnode_extension *mtnode_ext;
+ char *replica_root = (char*)slapi_entry_attr_get_charptr (e, attr_replicaRoot);
+ char buf [BUFSIZ];
+ char *errortext = errorbuf ? errorbuf : buf;
+
+ if (errorbuf)
+ {
+ errorbuf[0] = '\0';
+ }
+
+ *returncode = LDAP_SUCCESS;
+
+ PR_Lock (s_configLock);
+
+ /* add the dn to the dn hash so we can tell this replica is being configured */
+ replica_add_by_dn(replica_root);
+
+ mtnode_ext = _replica_config_get_mtnode_ext (e);
+ PR_ASSERT (mtnode_ext);
+
+ if (mtnode_ext->replica)
+ {
+ sprintf (errortext, "replica already configured for %s", replica_root);
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_config_add: %s\n", errortext);
+ *returncode = LDAP_UNWILLING_TO_PERFORM;
+ goto done;
+ }
+
+ /* create replica object */
+ r = replica_new_from_entry (e, errortext, PR_TRUE /* is a newly added entry */);
+ if (r == NULL)
+ {
+ *returncode = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+
+ /* Set the mapping tree node state, and the referrals from the RUV */
+ /* if this server is a 4.0 consumer the referrals are set by legacy plugin */
+ if (!replica_is_legacy_consumer (r))
+ consumer5_set_mapping_tree_state_for_replica(r, NULL);
+
+ /* ONREPL if replica is added as writable we need to execute protocol that
+ introduces new writable replica to the topology */
+
+ mtnode_ext->replica = object_new (r, replica_destroy); /* Refcnt is 1 */
+
+ /* add replica object to the hash */
+ *returncode = replica_add_by_name (replica_get_name (r), mtnode_ext->replica); /* Increments object refcnt */
+ /* delete the dn from the dn hash - done with configuration */
+ replica_delete_by_dn(replica_root);
+
+done:
+
+ PR_Unlock (s_configLock);
+ /* slapi_ch_free accepts NULL pointer */
+ slapi_ch_free ((void**)&replica_root);
+
+ if (*returncode != LDAP_SUCCESS)
+ {
+ if (mtnode_ext->replica)
+ object_release (mtnode_ext->replica);
+ return SLAPI_DSE_CALLBACK_ERROR;
+ }
+ else
+ return SLAPI_DSE_CALLBACK_OK;
+}
+
+static int
+replica_config_modify (Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e,
+ int *returncode, char *returntext, void *arg)
+{
+ int rc= 0;
+ LDAPMod **mods;
+ int i, apply_mods;
+ multimaster_mtnode_extension *mtnode_ext;
+ Replica *r = NULL;
+ char *replica_root = NULL;
+ char buf [BUFSIZ];
+ char *errortext = returntext ? returntext : buf;
+ char *config_attr, *config_attr_value;
+ Slapi_Operation *op;
+ void *identity;
+
+ if (returntext)
+ {
+ returntext[0] = '\0';
+ }
+ *returncode = LDAP_SUCCESS;
+
+ /* just let internal operations originated from replication plugin to go through */
+ slapi_pblock_get (pb, SLAPI_OPERATION, &op);
+ slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &identity);
+
+ if (operation_is_flag_set(op, OP_FLAG_INTERNAL) &&
+ (identity == repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION)))
+ {
+ *returncode = LDAP_SUCCESS;
+ return SLAPI_DSE_CALLBACK_OK;
+ }
+
+ replica_root = (char*)slapi_entry_attr_get_charptr (e, attr_replicaRoot);
+
+ PR_Lock (s_configLock);
+
+ mtnode_ext = _replica_config_get_mtnode_ext (e);
+ PR_ASSERT (mtnode_ext);
+
+ if (mtnode_ext->replica)
+ object_acquire (mtnode_ext->replica);
+
+ if (mtnode_ext->replica == NULL)
+ {
+ sprintf (errortext, "replica does not exist for %s", replica_root);
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_config_modify: %s\n",
+ errortext);
+ *returncode = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+
+ r = object_get_data (mtnode_ext->replica);
+ PR_ASSERT (r);
+
+ slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods);
+ for (apply_mods = 0; apply_mods <= 1; apply_mods++)
+ {
+ /* we only allow the replica ID and type to be modified together e.g.
+ if converting a read only replica to a master or vice versa -
+ we will need to change both the replica ID and the type at the same
+ time - we must disallow changing the replica ID if the type is not
+ being changed and vice versa
+ */
+ char *new_repl_id = NULL;
+ char *new_repl_type = NULL;
+
+ if (*returncode != LDAP_SUCCESS)
+ break;
+
+ for (i = 0; (mods[i] && (LDAP_SUCCESS == rc)); i++)
+ {
+ if (*returncode != LDAP_SUCCESS)
+ break;
+
+ config_attr = (char *) mods[i]->mod_type;
+ PR_ASSERT (config_attr);
+
+ /* disallow modifications or removal of replica root,
+ replica name and replica state attributes */
+ if (strcasecmp (config_attr, attr_replicaRoot) == 0 ||
+ strcasecmp (config_attr, attr_replicaName) == 0 ||
+ strcasecmp (config_attr, attr_state) == 0)
+ {
+ *returncode = LDAP_UNWILLING_TO_PERFORM;
+ sprintf (errortext, "modification of %s attribute is not allowed",
+ config_attr);
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_config_modify: %s\n",
+ errortext);
+ }
+ /* this is a request to delete an attribute */
+ else if (mods[i]->mod_op & LDAP_MOD_DELETE || mods[i]->mod_bvalues == NULL
+ || mods[i]->mod_bvalues[0]->bv_val == NULL)
+ {
+ /* currently, you can only remove referral,
+ legacy consumer or bind dn attribute */
+ if (strcasecmp (config_attr, attr_replicaBindDn) == 0)
+ {
+ *returncode = replica_config_change_updatedn (r, mods[i], errortext, apply_mods);
+ }
+ else if (strcasecmp (config_attr, attr_replicaReferral) == 0)
+ {
+ if (apply_mods) {
+ replica_set_referrals(r, NULL);
+ if (!replica_is_legacy_consumer (r)) {
+ consumer5_set_mapping_tree_state_for_replica(r, NULL);
+ }
+ }
+ }
+ else if (strcasecmp (config_attr, type_replicaLegacyConsumer) == 0)
+ {
+ if (apply_mods)
+ replica_set_legacy_consumer (r, PR_FALSE);
+ }
+ else
+ {
+ *returncode = LDAP_UNWILLING_TO_PERFORM;
+ sprintf (errortext, "deletion of %s attribute is not allowed", config_attr);
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_config_modify: %s\n",
+ errortext);
+ }
+ }
+ else /* modify an attribute */
+ {
+ config_attr_value = (char *) mods[i]->mod_bvalues[0]->bv_val;
+
+ if (strcasecmp (config_attr, attr_replicaBindDn) == 0)
+ {
+ *returncode = replica_config_change_updatedn (r, mods[i],
+ errortext, apply_mods);
+ }
+ else if (strcasecmp (config_attr, attr_replicaType) == 0)
+ {
+ new_repl_type = slapi_ch_strdup(config_attr_value);
+ }
+ else if (strcasecmp (config_attr, attr_replicaId) == 0)
+ {
+ new_repl_id = slapi_ch_strdup(config_attr_value);
+ }
+ else if (strcasecmp (config_attr, attr_flags) == 0)
+ {
+ *returncode = replica_config_change_flags (r, config_attr_value,
+ errortext, apply_mods);
+ }
+ else if (strcasecmp (config_attr, TASK_ATTR) == 0)
+ {
+ *returncode = replica_execute_task (mtnode_ext->replica, config_attr_value,
+ errortext, apply_mods);
+ }
+ else if (strcasecmp (config_attr, attr_replicaReferral) == 0)
+ {
+ if (apply_mods)
+ {
+ Slapi_Mod smod;
+ Slapi_ValueSet *vs= slapi_valueset_new();
+ slapi_mod_init_byref(&smod,mods[i]);
+ slapi_valueset_set_from_smod(vs, &smod);
+ replica_set_referrals (r, vs);
+ slapi_mod_done(&smod);
+ slapi_valueset_free(vs);
+ if (!replica_is_legacy_consumer (r)) {
+ consumer5_set_mapping_tree_state_for_replica(r, NULL);
+ }
+ }
+ }
+ else if (strcasecmp (config_attr, type_replicaPurgeDelay) == 0)
+ {
+ if (apply_mods && config_attr_value && config_attr_value[0])
+ {
+ PRUint32 delay;
+ if (isdigit (config_attr_value[0]))
+ {
+ delay = (unsigned int)atoi(config_attr_value);
+ replica_set_purge_delay(r, delay);
+ }
+ else
+ *returncode = LDAP_OPERATIONS_ERROR;
+ }
+ }
+ else if (strcasecmp (config_attr, type_replicaTombstonePurgeInterval) == 0)
+ {
+ if (apply_mods && config_attr_value && config_attr_value[0])
+ {
+ long interval;
+ interval = atol (config_attr_value);
+ replica_set_tombstone_reap_interval (r, interval);
+ }
+ }
+ else if (strcasecmp (config_attr, type_replicaLegacyConsumer) == 0)
+ {
+ if (apply_mods)
+ {
+ PRBool legacy = (strcasecmp (config_attr_value, "on") == 0) ||
+ (strcasecmp (config_attr_value, "true") == 0) ||
+ (strcasecmp (config_attr_value, "yes") == 0) ||
+ (strcasecmp (config_attr_value, "1") == 0);
+
+ replica_set_legacy_consumer (r, legacy);
+ }
+ }
+ /* ignore modifiers attributes added by the server */
+ else if (strcasecmp (config_attr, "modifytimestamp") == 0 ||
+ strcasecmp (config_attr, "modifiersname") == 0)
+ {
+ *returncode = LDAP_SUCCESS;
+ }
+ else
+ {
+ *returncode = LDAP_UNWILLING_TO_PERFORM;
+ sprintf (errortext, "modification of attribute %s is not allowed in replica entry", config_attr);
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_config_modify: %s\n",
+ errortext);
+ }
+ }
+ }
+
+ if (new_repl_id || new_repl_type)
+ {
+ *returncode = replica_config_change_type_and_id(r, new_repl_type,
+ new_repl_id, errortext,
+ apply_mods);
+ slapi_ch_free_string(&new_repl_id);
+ slapi_ch_free_string(&new_repl_type);
+ }
+ }
+
+done:
+ if (mtnode_ext->replica)
+ object_release (mtnode_ext->replica);
+
+ /* slapi_ch_free accepts NULL pointer */
+ slapi_ch_free ((void**)&replica_root);
+
+ PR_Unlock (s_configLock);
+
+ if (*returncode != LDAP_SUCCESS)
+ {
+ return SLAPI_DSE_CALLBACK_ERROR;
+ }
+ else
+ {
+ return SLAPI_DSE_CALLBACK_OK;
+ }
+}
+
+static int
+replica_config_delete (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter,
+ int *returncode, char *returntext, void *arg)
+{
+ multimaster_mtnode_extension *mtnode_ext;
+ Replica *r;
+
+ PR_Lock (s_configLock);
+
+ mtnode_ext = _replica_config_get_mtnode_ext (e);
+ PR_ASSERT (mtnode_ext);
+
+ if (mtnode_ext->replica)
+ {
+ /* remove object from the hash */
+ r = (Replica*)object_get_data (mtnode_ext->replica);
+ PR_ASSERT (r);
+ replica_delete_by_name (replica_get_name (r));
+ object_release (mtnode_ext->replica);
+ mtnode_ext->replica = NULL;
+ }
+
+ PR_Unlock (s_configLock);
+
+ *returncode = LDAP_SUCCESS;
+ return SLAPI_DSE_CALLBACK_OK;
+}
+
+static int
+replica_config_search (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode,
+ char *returntext, void *arg)
+{
+ multimaster_mtnode_extension *mtnode_ext;
+ int changeCount = 0;
+ char val [64];
+
+ /* add attribute that contains number of entries in the changelog for this replica */
+
+ PR_Lock (s_configLock);
+
+ /* if we have no changelog - we have no changes */
+ if (cl5GetState () == CL5_STATE_OPEN)
+ {
+ mtnode_ext = _replica_config_get_mtnode_ext (e);
+ PR_ASSERT (mtnode_ext);
+
+ if (mtnode_ext->replica)
+ {
+ object_acquire (mtnode_ext->replica);
+ changeCount = cl5GetOperationCount (mtnode_ext->replica);
+ object_release (mtnode_ext->replica);
+ }
+ }
+
+ sprintf (val, "%d", changeCount);
+ slapi_entry_add_string (e, type_replicaChangeCount, val);
+
+ PR_Unlock (s_configLock);
+
+ return SLAPI_DSE_CALLBACK_OK;
+}
+
+static int
+replica_config_change_type_and_id (Replica *r, const char *new_type,
+ const char *new_id, char *returntext,
+ int apply_mods)
+{
+ int type;
+ ReplicaType oldtype;
+ ReplicaId rid;
+ ReplicaId oldrid;
+
+ PR_ASSERT (r);
+
+ oldtype = replica_get_type(r);
+ oldrid = replica_get_rid(r);
+ if (new_type == NULL) /* by default - replica is read-only */
+ {
+ type = REPLICA_TYPE_READONLY;
+ }
+ else
+ {
+ type = atoi (new_type);
+ if (type <= REPLICA_TYPE_UNKNOWN || type >= REPLICA_TYPE_END)
+ {
+ sprintf (returntext, "invalid replica type %d", type);
+ return LDAP_OPERATIONS_ERROR;
+ }
+ }
+
+ /* disallow changing type to itself just to permit a replica ID change */
+ if (oldtype == type)
+ {
+ sprintf (returntext, "replica type is already %d - not changing", type);
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+ if (type == REPLICA_TYPE_READONLY)
+ {
+ rid = READ_ONLY_REPLICA_ID; /* default rid for read only */
+ }
+ else if (!new_id)
+ {
+ sprintf(returntext, "a replica ID is required when changing replica type to read-write");
+ return LDAP_UNWILLING_TO_PERFORM;
+ }
+ else
+ {
+ int temprid = atoi (new_id);
+ if (temprid <= 0 || temprid >= READ_ONLY_REPLICA_ID)
+ {
+ sprintf(returntext,
+ "attribute %s must have a value greater than 0 "
+ "and less than %d",
+ attr_replicaId, READ_ONLY_REPLICA_ID);
+ return LDAP_UNWILLING_TO_PERFORM;
+ }
+ else
+ {
+ rid = (ReplicaId)temprid;
+ }
+ }
+
+ /* error if old rid == new rid */
+ if (oldrid == rid)
+ {
+ sprintf (returntext, "replica ID is already %d - not changing", rid);
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+ if (apply_mods)
+ {
+ replica_set_type (r, type);
+ replica_set_rid(r, rid);
+
+ /* Set the mapping tree node, and the list of referrals */
+ /* if this server is a 4.0 consumer the referrals are set by legacy plugin */
+ if (!replica_is_legacy_consumer(r))
+ consumer5_set_mapping_tree_state_for_replica(r, NULL);
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+replica_config_change_updatedn (Replica *r, const LDAPMod *mod, char *returntext,
+ int apply_mods)
+{
+ PR_ASSERT (r);
+
+ if (apply_mods)
+ {
+ Slapi_Mod smod;
+ Slapi_ValueSet *vs= slapi_valueset_new();
+ slapi_mod_init_byref(&smod, (LDAPMod *)mod); /* cast away const */
+ slapi_valueset_set_from_smod(vs, &smod);
+ replica_set_updatedn(r, vs, mod->mod_op);
+ slapi_mod_done(&smod);
+ slapi_valueset_free(vs);
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int replica_config_change_flags (Replica *r, const char *new_flags,
+ char *returntext, int apply_mods)
+{
+ PR_ASSERT (r);
+
+ if (apply_mods)
+ {
+ PRUint32 flags;
+
+ flags = atol (new_flags);
+
+ replica_replace_flags (r, flags);
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int replica_execute_task (Object *r, const char *task_name, char *returntext,
+ int apply_mods)
+{
+
+ if (strcasecmp (task_name, CL2LDIF_TASK) == 0)
+ {
+ if (apply_mods)
+ {
+ return replica_execute_cl2ldif_task (r, returntext);
+ }
+ else
+ return LDAP_SUCCESS;
+ }
+ else if (strncasecmp (task_name, CLEANRUV, CLEANRUVLEN) == 0)
+ {
+ int temprid = atoi(&(task_name[CLEANRUVLEN]));
+ if (temprid <= 0 || temprid >= READ_ONLY_REPLICA_ID){
+ sprintf(returntext, "Invalid replica id for task - %s", task_name);
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "replica_execute_task: %s\n", returntext);
+ return LDAP_OPERATIONS_ERROR;
+ }
+ if (apply_mods)
+ {
+ return replica_execute_cleanruv_task (r, (ReplicaId)temprid, returntext);
+ }
+ else
+ return LDAP_SUCCESS;
+ }
+ else
+ {
+ sprintf (returntext, "unsupported replica task - %s", task_name);
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "replica_execute_task: %s\n", returntext);
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+}
+
+static int replica_execute_cl2ldif_task (Object *r, char *returntext)
+{
+ int rc;
+ Object *rlist [2];
+ Replica *replica;
+ char fName [MAXPATHLEN];
+ char *clDir;
+
+ if (cl5GetState () != CL5_STATE_OPEN)
+ {
+ sprintf (returntext, "changelog is not open");
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "replica_execute_cl2ldif_task: %s\n", returntext);
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+ rlist[0] = r;
+ rlist[1] = NULL;
+
+ /* file is stored in the changelog directory and is named
+ <replica name>.ldif */
+ clDir = cl5GetDir ();
+ PR_ASSERT (clDir);
+
+ replica = (Replica*)object_get_data (r);
+ PR_ASSERT (replica);
+
+ sprintf (fName, "%s/%s.ldif", clDir, replica_get_name (replica));
+ slapi_ch_free ((void**)&clDir);
+
+ rc = cl5ExportLDIF (fName, rlist);
+ if (rc != CL5_SUCCESS)
+ {
+ sprintf (returntext, "failed to export changelog data to file %s; "
+ "changelog error - %d", fName, rc);
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "replica_execute_cl2ldif_task: %s\n", returntext);
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static multimaster_mtnode_extension *
+_replica_config_get_mtnode_ext (const Slapi_Entry *e)
+{
+ const char *replica_root;
+ Slapi_DN *sdn = NULL;
+ mapping_tree_node *mtnode;
+ multimaster_mtnode_extension *ext = NULL;
+ char ebuf[BUFSIZ];
+
+ /* retirve root of the tree for which replica is configured */
+ replica_root = slapi_entry_attr_get_charptr (e, attr_replicaRoot);
+ if (replica_root == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_config_add: "
+ "configuration entry %s missing %s attribute\n",
+ escape_string(slapi_entry_get_dn((Slapi_Entry *)e), ebuf),
+ attr_replicaRoot);
+ return NULL;
+ }
+
+ sdn = slapi_sdn_new_dn_passin (replica_root);
+
+ /* locate mapping tree node for the specified subtree */
+ mtnode = slapi_get_mapping_tree_node_by_dn (sdn);
+ if (mtnode == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_config_add: "
+ "failed to locate mapping tree node for dn %s\n",
+ escape_string(slapi_sdn_get_dn(sdn), ebuf));
+ }
+ else
+ {
+ /* check if replica object already exists for the specified subtree */
+ ext = (multimaster_mtnode_extension *)repl_con_get_ext (REPL_CON_EXT_MTNODE, mtnode);
+ }
+
+ slapi_sdn_free (&sdn);
+
+ return ext;
+}
+
+static int
+replica_execute_cleanruv_task (Object *r, ReplicaId rid, char *returntext)
+{
+ int rc = 0;
+ Object *RUVObj;
+ RUV *local_ruv = NULL;
+ Replica *replica = (Replica*)object_get_data (r);
+
+ PR_ASSERT (replica);
+
+ RUVObj = replica_get_ruv(replica);
+ PR_ASSERT(RUVObj);
+ local_ruv = (RUV*)object_get_data (RUVObj);
+ /* Need to check that :
+ * - rid is not the local one
+ * - rid is not the last one
+ */
+ if ((replica_get_rid(replica) == rid) ||
+ (ruv_replica_count(local_ruv) <= 1)) {
+ return LDAP_UNWILLING_TO_PERFORM;
+ }
+ rc = ruv_delete_replica(local_ruv, rid);
+ replica_set_ruv_dirty(replica);
+ replica_write_ruv(replica);
+ object_release(RUVObj);
+
+ /* Update Mapping Tree to reflect RUV changes */
+ consumer5_set_mapping_tree_state_for_replica(replica, NULL);
+
+ if (rc != RUV_SUCCESS){
+ return LDAP_OPERATIONS_ERROR;
+ }
+ return LDAP_SUCCESS;
+}
+
+
diff --git a/ldap/servers/plugins/replication/repl5_replica_dnhash.c b/ldap/servers/plugins/replication/repl5_replica_dnhash.c
new file mode 100644
index 00000000..4b9f42b9
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl5_replica_dnhash.c
@@ -0,0 +1,189 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* repl5_replica_dnhash.c */
+
+#include "repl5.h"
+#include "plhash.h"
+
+/* global data */
+static PLHashTable *s_hash;
+static PRRWLock *s_lock;
+
+/* Forward declarations */
+static PRIntn replica_destroy_hash_entry (PLHashEntry *he, PRIntn index, void *arg);
+
+int replica_init_dn_hash ()
+{
+ /* allocate table */
+ s_hash = PL_NewHashTable(0, PL_HashString, PL_CompareStrings,
+ PL_CompareValues, NULL, NULL);
+ if (s_hash == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_init_dn_hash: "
+ "failed to allocate hash table; NSPR error - %d\n",
+ PR_GetError ());
+ return -1;
+ }
+
+ /* create lock */
+ s_lock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "replica_dnhash_lock");
+ if (s_lock == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_init_dn_hash: "
+ "failed to create lock; NSPR error - %d\n",
+ PR_GetError ());
+ replica_destroy_dn_hash ();
+ return -1;
+ }
+
+ return 0;
+}
+
+void replica_destroy_dn_hash ()
+{
+ /* destroy the content */
+ PL_HashTableEnumerateEntries(s_hash, replica_destroy_hash_entry, NULL);
+
+ if (s_hash)
+ PL_HashTableDestroy(s_hash);
+
+ if (s_lock)
+ PR_DestroyRWLock (s_lock);
+}
+
+int replica_add_by_dn (const char *dn)
+{
+ char *dn_copy = NULL;
+
+ if (dn == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_add_by_dn: NULL argument\n");
+ return -1;
+ }
+
+ if (s_hash == NULL || s_lock == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_add_by_dn: "
+ "replica hash is not initialized\n");
+ return -1;
+ }
+
+ PR_RWLock_Wlock (s_lock);
+
+ /* make sure that the dn is unique */
+ if (PL_HashTableLookup(s_hash, dn) != NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_add_by_dn: "
+ "replica with dn (%s) already in the hash\n", dn);
+ PR_RWLock_Unlock (s_lock);
+ return -1 ;
+ }
+
+ /* add dn */
+ dn_copy = slapi_ch_strdup(dn);
+ if (PL_HashTableAdd(s_hash, dn_copy, dn_copy) == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_add_by_dn: "
+ "failed to add dn (%s); NSPR error - %d\n",
+ dn_copy, PR_GetError ());
+ slapi_ch_free((void **)&dn_copy);
+ PR_RWLock_Unlock (s_lock);
+ return -1;
+ }
+
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "replica_add_by_dn: "
+ "added dn (%s)\n",
+ dn_copy);
+ PR_RWLock_Unlock (s_lock);
+ return 0;
+}
+
+int replica_delete_by_dn (const char *dn)
+{
+ char *dn_copy = NULL;
+
+ if (dn == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_delete_by_dn: "
+ "NULL argument\n");
+ return -1;
+ }
+
+ if (s_hash == NULL || s_lock == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_delete_by_dn: "
+ "replica hash is not initialized\n");
+ return -1;
+ }
+
+ PR_RWLock_Wlock (s_lock);
+
+ /* locate object */
+ if (NULL == (dn_copy = (char *)PL_HashTableLookup(s_hash, dn)))
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_delete_by_dn: "
+ "dn (%s) is not in the hash.\n", dn);
+ PR_RWLock_Unlock (s_lock);
+ return -1;
+ }
+
+ /* remove from hash */
+ PL_HashTableRemove(s_hash, dn);
+ slapi_ch_free((void **)&dn_copy);
+
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "replica_delete_by_dn: "
+ "removed dn (%s)\n",
+ dn);
+ PR_RWLock_Unlock (s_lock);
+
+ return 0;
+}
+
+int replica_is_being_configured (const char *dn)
+{
+ if (dn == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_is_dn_in_hash: "
+ "NULL argument\n");
+ return 0;
+ }
+
+ if (s_hash == NULL || s_lock == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_is_dn_in_hash: "
+ "dn hash is not initialized\n");
+ return 0;
+ }
+
+ PR_RWLock_Wlock (s_lock);
+
+ /* locate object */
+ if (NULL == PL_HashTableLookup(s_hash, dn))
+ {
+ PR_RWLock_Unlock (s_lock);
+ return 0;
+ }
+
+ PR_RWLock_Unlock (s_lock);
+
+ return 1;
+}
+
+/* Helper functions */
+
+/* this function called for each hash node during hash destruction */
+static PRIntn replica_destroy_hash_entry (PLHashEntry *he, PRIntn index, void *arg)
+{
+ char *dn_copy;
+
+ if (he == NULL)
+ return HT_ENUMERATE_NEXT;
+
+ dn_copy = (char*)he->value;
+ slapi_ch_free((void **)&dn_copy);
+
+ return HT_ENUMERATE_REMOVE;
+}
diff --git a/ldap/servers/plugins/replication/repl5_replica_hash.c b/ldap/servers/plugins/replication/repl5_replica_hash.c
new file mode 100644
index 00000000..92ea87f4
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl5_replica_hash.c
@@ -0,0 +1,243 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+/* repl5_replica_hash.c */
+
+#include "repl5.h"
+#include "plhash.h"
+
+/* global data */
+static PLHashTable *s_hash;
+static PRRWLock *s_lock;
+
+struct repl_enum_data
+{
+ FNEnumReplica fn;
+ void *arg;
+};
+
+/* Forward declarations */
+static PRIntn replica_destroy_hash_entry (PLHashEntry *he, PRIntn index, void *arg);
+static PRIntn replica_enumerate (PLHashEntry *he, PRIntn index, void *hash_data);
+
+
+int replica_init_name_hash ()
+{
+ /* allocate table */
+ s_hash = PL_NewHashTable(0, PL_HashString, PL_CompareStrings,
+ PL_CompareValues, NULL, NULL);
+ if (s_hash == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_init_name_hash: "
+ "failed to allocate hash table; NSPR error - %d\n",
+ PR_GetError ());
+ return -1;
+ }
+
+ /* create lock */
+ s_lock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "replica_hash_lock");
+ if (s_lock == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_init_name_hash: "
+ "failed to create lock; NSPR error - %d\n",
+ PR_GetError ());
+ replica_destroy_name_hash ();
+ return -1;
+ }
+
+ return 0;
+}
+
+void replica_destroy_name_hash ()
+{
+ /* destroy the content */
+ PL_HashTableEnumerateEntries(s_hash, replica_destroy_hash_entry, NULL);
+
+ if (s_hash)
+ PL_HashTableDestroy(s_hash);
+
+ if (s_lock)
+ PR_DestroyRWLock (s_lock);
+}
+
+int replica_add_by_name (const char *name, Object *replica)
+{
+ if (name == NULL || replica == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_add_by_name: NULL argument\n");
+ return -1;
+ }
+
+ if (s_hash == NULL || s_lock == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_add_by_name: "
+ "replica hash is not initialized\n");
+ return -1;
+ }
+
+ PR_RWLock_Wlock (s_lock);
+
+ /* make sure that the name is unique */
+ if (PL_HashTableLookup(s_hash, name) != NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_add_by_name: "
+ "replica with name (%s) already in the hash\n", name);
+ PR_RWLock_Unlock (s_lock);
+ return -1 ;
+ }
+
+ /* acquire replica object */
+ object_acquire (replica);
+
+ /* add replica */
+ if (PL_HashTableAdd(s_hash, name, replica) == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_add_by_name: "
+ "failed to add replica with name (%s); NSPR error - %d\n",
+ name, PR_GetError ());
+ object_release (replica);
+ PR_RWLock_Unlock (s_lock);
+ return -1;
+ }
+
+ PR_RWLock_Unlock (s_lock);
+ return 0;
+}
+
+int replica_delete_by_name (const char *name)
+{
+ Object *replica;
+
+ if (name == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_delete_by_name: "
+ "NULL argument\n");
+ return -1;
+ }
+
+ if (s_hash == NULL || s_lock == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_delete_by_name: "
+ "replica hash is not initialized\n");
+ return -1;
+ }
+
+ PR_RWLock_Wlock (s_lock);
+
+ /* locate object */
+ replica = (Object*)PL_HashTableLookup(s_hash, name);
+ if (replica == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_delete_by_name: "
+ "replica with name (%s) is not in the hash.\n", name);
+ PR_RWLock_Unlock (s_lock);
+ return -1;
+ }
+
+ /* remove from hash */
+ PL_HashTableRemove(s_hash, name);
+
+ /* release replica */
+ object_release (replica);
+
+ PR_RWLock_Unlock (s_lock);
+
+ return 0;
+}
+
+Object* replica_get_by_name (const char *name)
+{
+ Object *replica;
+
+ if (name == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_get_by_name: "
+ "NULL argument\n");
+ return NULL;
+ }
+
+ if (s_hash == NULL || s_lock == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_get_by_name: "
+ "replica hash is not initialized\n");
+ return NULL;
+ }
+
+ PR_RWLock_Rlock (s_lock);
+
+ /* locate object */
+ replica = (Object*)PL_HashTableLookup(s_hash, name);
+ if (replica == NULL)
+ {
+ PR_RWLock_Unlock (s_lock);
+ return NULL;
+ }
+
+ object_acquire (replica);
+
+ PR_RWLock_Unlock (s_lock);
+
+ return replica;
+}
+
+void replica_enumerate_replicas (FNEnumReplica fn, void *arg)
+{
+ struct repl_enum_data data;
+
+ PR_ASSERT (fn);
+
+ data.fn = fn;
+ data.arg = arg;
+
+ PR_RWLock_Wlock (s_lock);
+ PL_HashTableEnumerateEntries(s_hash, replica_enumerate, &data);
+ PR_RWLock_Unlock (s_lock);
+}
+
+/* Helper functions */
+
+/* this function called for each hash node during hash destruction */
+static PRIntn replica_destroy_hash_entry (PLHashEntry *he, PRIntn index, void *arg)
+{
+ Object *r_obj;
+ Replica *r;
+
+ if (he == NULL)
+ return HT_ENUMERATE_NEXT;
+
+ r_obj = (Object*)he->value;
+ r = (Replica*)object_get_data (r_obj);
+ PR_ASSERT (r);
+
+ /* flash replica state to the disk */
+ replica_flush (r);
+
+ /* release replica object */
+ object_release (r_obj);
+
+ return HT_ENUMERATE_REMOVE;
+}
+
+static PRIntn replica_enumerate (PLHashEntry *he, PRIntn index, void *hash_data)
+{
+ Object *r_obj;
+ Replica *r;
+ struct repl_enum_data *data = hash_data;
+
+ r_obj = (Object*)he->value;
+ PR_ASSERT (r_obj);
+
+ object_acquire (r_obj);
+ r = (Replica*)object_get_data (r_obj);
+ PR_ASSERT (r);
+
+ data->fn (r, data->arg);
+
+ object_release (r_obj);
+
+ return HT_ENUMERATE_NEXT;
+}
+
diff --git a/ldap/servers/plugins/replication/repl5_replsupplier.c b/ldap/servers/plugins/replication/repl5_replsupplier.c
new file mode 100644
index 00000000..e98d0e58
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl5_replsupplier.c
@@ -0,0 +1,166 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* repl5_replsupplier.c */
+/*
+
+A replsupplier is an object that knows how to manage outbound replication
+for one consumer.
+
+Methods:
+init()
+configure()
+start()
+stop()
+destroy()
+status()
+notify()
+
+*/
+
+#include "slapi-plugin.h"
+#include "repl5.h"
+
+typedef struct repl_supplier {
+ PRUint32 client_change_count; /* # of client-supplied changes */
+ PRUint32 repl_change_count; /* # of replication updates */
+
+ PRLock *lock;
+
+} repl_supplier;
+
+
+static void repl_supplier_free(Repl_Supplier **rsp);
+
+/*
+ * Create and initialize this replsupplier object.
+ */
+Repl_Supplier *
+replsupplier_init(Slapi_Entry *e)
+{
+ Repl_Supplier *rs;
+
+ if ((rs = (Repl_Supplier *)slapi_ch_malloc(sizeof(Repl_Supplier))) == NULL)
+ {
+ goto loser;
+ }
+ if ((rs->lock = PR_NewLock()) == NULL)
+ {
+ goto loser;
+ }
+ return rs;
+
+loser:
+ repl_supplier_free(&rs);
+ return NULL;
+}
+
+
+
+static void
+repl_supplier_free(Repl_Supplier **rsp)
+{
+ if (NULL != rsp)
+ {
+ Repl_Supplier *rs = *rsp;
+ if (NULL != rs)
+ {
+ if (NULL != rs->lock)
+ {
+ PR_DestroyLock(rs->lock);
+ rs->lock = NULL;
+ }
+ slapi_ch_free((void **)rsp);
+ }
+ }
+}
+
+
+
+/*
+ * Configure a repl_supplier object.
+ */
+void
+replsupplier_configure(Repl_Supplier *rs, Slapi_PBlock *pb)
+{
+ PR_ASSERT(NULL != rs);
+
+}
+
+
+
+/*
+ * Start a repl_supplier object. This means that it's ok for
+ * the repl_supplier to begin normal replication duties. It does
+ * not necessarily mean that a replication session will occur
+ * immediately.
+ */
+void
+replsupplier_start(Repl_Supplier *rs)
+{
+ PR_ASSERT(NULL != rs);
+}
+
+
+
+
+/*
+ * Stop a repl_supplier object. This causes any active replication
+ * sessions to be stopped ASAP, and puts the repl_supplier into a
+ * stopped state. No additional replication activity will occur
+ * until the replsupplier_start() function is called.
+ */
+void
+replsupplier_stop(Repl_Supplier *rs)
+{
+ PR_ASSERT(NULL != rs);
+}
+
+
+
+
+/*
+ * Destroy a repl_supplier object. The object will be stopped, if it
+ * is not already stopped.
+ */
+void
+replsupplier_destroy(Repl_Supplier **rsp)
+{
+ Repl_Supplier *rs;
+
+ PR_ASSERT(NULL != rsp && NULL != *rsp);
+
+ rs = *rsp;
+
+ slapi_ch_free((void **)rsp);
+}
+
+
+
+/*
+ * This method should be called by the repl_bos whenever it determines
+ * that a change to the replicated area serviced by this repl_supplier
+ * has occurred. This gives the repl_supplier a chance to implement a
+ * scheduling policy.
+ */
+void
+replsupplier_notify(Repl_Supplier *rs, PRUint32 eventmask)
+{
+ PR_ASSERT(NULL != rs);
+}
+
+
+
+/*
+ * This method is used to obtain the status of this particular
+ * repl_supplier object. Eventually it will return some object containing
+ * status information. For now, it's just a placeholder function.
+ */
+PRUint32
+replsupplier_get_status(Repl_Supplier *rs)
+{
+ PR_ASSERT(NULL != rs);
+ return 0;
+}
diff --git a/ldap/servers/plugins/replication/repl5_ruv.c b/ldap/servers/plugins/replication/repl5_ruv.c
new file mode 100644
index 00000000..b8cb79c9
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl5_ruv.c
@@ -0,0 +1,2022 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+/* repl5_ruv.c - implementation of replica update vector */
+/*
+ * The replica update vector is stored in the nsds50ruv attribute. The LDIF
+ * representation of the ruv is:
+ * nsds50ruv: {replicageneration} <gen-id-for-this-replica>
+ * nsds50ruv: {replica <rid>[ <url>]}[ <mincsn> <maxcsn>]
+ * nsds50ruv: {replica <rid>[ <url>]}[ <mincsn> <maxcsn>]
+ * ...
+ * nsds50ruv: {replica <rid>[ <url>]}[ <mincsn> <maxcsn>]
+ *
+ * nsruvReplicaLastModified: {replica <rid>[ <url>]} lastModifiedTime
+ * nsruvReplicaLastModified: {replica <rid>[ <url>]} lastModifiedTime
+ * ...
+ *
+ * For readability, ruv_dump appends nsruvReplicaLastModified to nsds50ruv:
+ * nsds50ruv: {replica <rid>[ <url>]}[ <mincsn> <maxcsn> [<lastModifiedTime>]]
+ */
+
+#include <string.h>
+#include <ctype.h> /* For isdigit() */
+#include "csnpl.h"
+#include "repl5_ruv.h"
+#include "repl_shared.h"
+#include "repl5.h"
+
+#define RIDSTR_SIZE 16 /* string large enough to hold replicaid*/
+#define RUVSTR_SIZE 256 /* string large enough to hold ruv and lastmodifiedtime */
+
+/* Data Structures */
+
+/* replica */
+typedef struct ruvElement
+{
+ ReplicaId rid; /* replica id for this element */
+ CSN *csn; /* largest csn that we know about that originated at the master */
+ CSN *min_csn; /* smallest csn that originated at the master */
+ char *replica_purl; /* Partial URL for replica */
+ CSNPL *csnpl; /* list of operations in progress */
+ time_t last_modified; /* timestamp the modification of csn */
+} RUVElement;
+
+/* replica update vector */
+struct _ruv
+{
+ char *replGen; /* replicated area generation: identifies replica
+ in space and in time */
+ DataList *elements; /* replicas */
+ PRRWLock *lock; /* concurrency control */
+};
+
+/* forward declarations */
+static int ruvInit (RUV **ruv, int initCount);
+static void ruvFreeReplica (void **data);
+static RUVElement* ruvGetReplica (const RUV *ruv, ReplicaId rid);
+static RUVElement* ruvAddReplica (RUV *ruv, const CSN *csn, const char *replica_purl);
+static RUVElement* ruvAddReplicaNoCSN (RUV *ruv, ReplicaId rid, const char *replica_purl);
+static RUVElement* ruvAddIndexReplicaNoCSN (RUV *ruv, ReplicaId rid, const char *replica_purl, int index);
+static int ruvReplicaCompare (const void *el1, const void *el2);
+static RUVElement *get_ruvelement_from_berval(const struct berval *bval);
+static char *get_replgen_from_berval(const struct berval *bval);
+
+static const char * const prefix_replicageneration = "{replicageneration}";
+static const char * const prefix_ruvcsn = "{replica "; /* intentionally missing '}' */
+
+
+/* API implementation */
+
+
+/*
+ * Allocate a new ruv and set its replica generation to the given generation.
+ */
+int
+ruv_init_new(const char *replGen, ReplicaId rid, const char *purl, RUV **ruv)
+{
+ int rc;
+ RUVElement *replica;
+
+ if (ruv == NULL || replGen == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_init_new: NULL argument\n");
+ return RUV_BAD_DATA;
+ }
+
+ rc = ruvInit (ruv, 0);
+ if (rc != RUV_SUCCESS)
+ return rc;
+
+ (*ruv)->replGen = slapi_ch_strdup (replGen);
+
+ /* we want to add the local writable replica to the RUV before any csns are created */
+ /* this is so that it can be referred to even before it accepted any changes */
+ if (purl)
+ {
+ replica = ruvAddReplicaNoCSN (*ruv, rid, purl);
+
+ if (replica == NULL)
+ return RUV_MEMORY_ERROR;
+ }
+
+ return RUV_SUCCESS;
+}
+
+
+/*
+ * Create a new RUV and initialize its contents from the provided Slapi_Attr.
+ * Returns:
+ * RUV_BAD_DATA if the values in the attribute were malformed.
+ * RUV_SUCCESS if all went well
+ */
+int
+ruv_init_from_slapi_attr(Slapi_Attr *attr, RUV **ruv)
+{
+ ReplicaId dummy = 0;
+
+ return (ruv_init_from_slapi_attr_and_check_purl(attr, ruv, &dummy));
+}
+
+/*
+ * Create a new RUV and initialize its contents from the provided Slapi_Attr.
+ * Returns:
+ * RUV_BAD_DATA if the values in the attribute were malformed.
+ * RUV_SUCCESS if all went well
+ * contain_purl is 0 if the ruv doesn't contain the local purl
+ * contain_purl is != 0 if the ruv contains the local purl (contains the rid)
+ */
+int
+ruv_init_from_slapi_attr_and_check_purl(Slapi_Attr *attr, RUV **ruv, ReplicaId *contain_purl)
+{
+ int return_value;
+
+ PR_ASSERT(NULL != attr && NULL != ruv);
+
+ if (NULL == ruv || NULL == attr)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "ruv_init_from_slapi_attr: NULL argument\n");
+ return_value = RUV_BAD_DATA;
+ }
+ else
+ {
+ int rc;
+ int numvalues;
+ slapi_attr_get_numvalues(attr, &numvalues);
+ if ((rc = ruvInit(ruv, numvalues)) != RUV_SUCCESS)
+ {
+ return_value = rc;
+ }
+ else
+ {
+ int hint;
+ Slapi_Value *value;
+ const struct berval *bval;
+ const char *purl = NULL;
+
+ return_value = RUV_SUCCESS;
+
+ purl = multimaster_get_local_purl();
+ *contain_purl = 0;
+
+ for (hint = slapi_attr_first_value(attr, &value);
+ hint != -1; hint = slapi_attr_next_value(attr, hint, &value))
+ {
+ bval = slapi_value_get_berval(value);
+ if (NULL != bval && NULL != bval->bv_val)
+ {
+ if (strncmp(bval->bv_val, prefix_replicageneration, strlen(prefix_replicageneration)) == 0) {
+ if (NULL == (*ruv)->replGen)
+ {
+ (*ruv)->replGen = get_replgen_from_berval(bval);
+ } else {
+ /* Twice replicageneration is wrong, just log and ignore */
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "ruv_init_from_slapi_attr: %s is present more than once\n",
+ prefix_replicageneration);
+ }
+ }
+ else
+ {
+ RUVElement *ruve = get_ruvelement_from_berval(bval);
+ if (NULL != ruve)
+ {
+ /* Is the local purl already in the ruv ? */
+ if ( (*contain_purl==0) && (strncmp(ruve->replica_purl, purl, strlen(purl))==0) )
+ {
+ *contain_purl = ruve->rid;
+ }
+ dl_add ((*ruv)->elements, ruve);
+ }
+ }
+ }
+ }
+ }
+ }
+ return return_value;
+}
+
+
+
+/*
+ * Same as ruv_init_from_slapi_attr, but takes an array of pointers to bervals.
+ * I wish this wasn't a cut-n-paste of the above function, but I don't see a
+ * clean way to define one API in terms of the other.
+ */
+int
+ruv_init_from_bervals(struct berval **vals, RUV **ruv)
+{
+ int return_value;
+
+ PR_ASSERT(NULL != vals && NULL != ruv);
+
+ if (NULL == ruv || NULL == vals)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "ruv_init_from_slapi_value: NULL argument\n");
+ return_value = RUV_BAD_DATA;
+ }
+ else
+ {
+ int i, rc;
+ i = 0;
+ while (vals[i] != NULL)
+ {
+ i++;
+ }
+ if ((rc = ruvInit (ruv, i)) != RUV_SUCCESS)
+ {
+ return_value = rc;
+ }
+ else
+ {
+ return_value = RUV_SUCCESS;
+ for (i = 0; NULL != vals[i]; i++)
+ {
+ if (NULL != vals[i]->bv_val)
+ {
+ if (strncmp(vals[i]->bv_val, prefix_replicageneration, strlen(prefix_replicageneration)) == 0) {
+ if (NULL == (*ruv)->replGen)
+ {
+ (*ruv)->replGen = get_replgen_from_berval(vals[i]);
+ } else {
+ /* Twice replicageneration is wrong, just log and ignore */
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "ruv_init_from_slapi_value: %s is present more than once\n",
+ prefix_replicageneration);
+ }
+ }
+ else
+ {
+ RUVElement *ruve = get_ruvelement_from_berval(vals[i]);
+ if (NULL != ruve)
+ {
+ dl_add ((*ruv)->elements, ruve);
+ }
+ }
+ }
+ }
+ }
+ }
+ return return_value;
+}
+
+
+
+RUV*
+ruv_dup (const RUV *ruv)
+{
+ int rc;
+ RUVElement *replica, *dupReplica;
+ int cookie;
+ RUV *dupRUV = NULL;
+
+ if (ruv == NULL)
+ return NULL;
+
+ PR_RWLock_Rlock (ruv->lock);
+
+ rc = ruvInit (&dupRUV, dl_get_count (ruv->elements));
+ if (rc != RUV_SUCCESS || dupRUV == NULL)
+ goto done;
+
+ dupRUV->replGen = slapi_ch_strdup (ruv->replGen);
+
+ for (replica = dl_get_first (ruv->elements, &cookie); replica;
+ replica = dl_get_next (ruv->elements, &cookie))
+ {
+ dupReplica = (RUVElement *)slapi_ch_calloc (1, sizeof (*dupReplica));
+ dupReplica->rid = replica->rid;
+ if (replica->csn)
+ dupReplica->csn = csn_dup (replica->csn);
+ if (replica->min_csn)
+ dupReplica->min_csn = csn_dup (replica->min_csn);
+ if (replica->replica_purl)
+ dupReplica->replica_purl = slapi_ch_strdup (replica->replica_purl);
+ dupReplica->last_modified = replica->last_modified;
+
+ /* ONREPL - we don't make copy of the pernding list. For now
+ we don't need it. */
+
+ dl_add (dupRUV->elements, dupReplica);
+ }
+
+done:
+ PR_RWLock_Unlock (ruv->lock);
+
+ return dupRUV;
+}
+
+void
+ruv_destroy (RUV **ruv)
+{
+ if (ruv != NULL && *ruv != NULL)
+ {
+ if ((*ruv)->elements)
+ {
+ dl_cleanup ((*ruv)->elements, ruvFreeReplica);
+ dl_free (&(*ruv)->elements);
+ }
+ /* slapi_ch_free accepts NULL pointer */
+ slapi_ch_free ((void **)&((*ruv)->replGen));
+
+ if ((*ruv)->lock)
+ {
+ PR_DestroyRWLock ((*ruv)->lock);
+ }
+
+ slapi_ch_free ((void**)ruv);
+ }
+}
+
+/*
+ * [610948]
+ * copy elements in srcruv to destruv
+ * destruv is the live wrapper, which could be referred by other threads.
+ * srcruv is cleaned up after copied.
+ */
+void
+ruv_copy_and_destroy (RUV **srcruv, RUV **destruv)
+{
+ DataList *elemp = NULL;
+ char *replgp = NULL;
+
+ if (NULL == srcruv || NULL == *srcruv || NULL == destruv)
+ {
+ return;
+ }
+
+ if (NULL == *destruv)
+ {
+ *destruv = *srcruv;
+ *srcruv = NULL;
+ }
+ else
+ {
+ PR_RWLock_Wlock((*destruv)->lock);
+ elemp = (*destruv)->elements;
+ (*destruv)->elements = (*srcruv)->elements;
+ if (elemp)
+ {
+ dl_cleanup (elemp, ruvFreeReplica);
+ dl_free (&elemp);
+ }
+
+ /* slapi_ch_free accepts NULL pointer */
+ replgp = (*destruv)->replGen;
+ (*destruv)->replGen = (*srcruv)->replGen;
+ slapi_ch_free ((void **)&replgp);
+
+ if ((*srcruv)->lock)
+ {
+ PR_DestroyRWLock ((*srcruv)->lock);
+ }
+ slapi_ch_free ((void**)srcruv);
+
+ PR_RWLock_Unlock((*destruv)->lock);
+ }
+ PR_ASSERT (*destruv != NULL && *srcruv == NULL);
+}
+
+int
+ruv_delete_replica (RUV *ruv, ReplicaId rid)
+{
+ int return_value;
+ if (ruv == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_delete_replica: NULL argument\n");
+ return_value = RUV_BAD_DATA;
+ }
+ else
+ {
+ /* check for duplicates */
+ PR_RWLock_Wlock (ruv->lock);
+ dl_delete (ruv->elements, (const void*)&rid, ruvReplicaCompare, ruvFreeReplica);
+ PR_RWLock_Unlock (ruv->lock);
+ return_value = RUV_SUCCESS;
+ }
+ return return_value;
+}
+
+int
+ruv_add_replica (RUV *ruv, ReplicaId rid, const char *replica_purl)
+{
+ RUVElement* replica;
+
+ PR_ASSERT (ruv && replica_purl);
+
+ PR_RWLock_Wlock (ruv->lock);
+ replica = ruvGetReplica (ruv, rid);
+ if (replica == NULL)
+ {
+ replica = ruvAddReplicaNoCSN (ruv, rid, replica_purl);
+ }
+
+ PR_RWLock_Unlock (ruv->lock);
+
+ if (replica)
+ return RUV_SUCCESS;
+ else
+ return RUV_MEMORY_ERROR;
+}
+
+int
+ruv_replace_replica_purl (RUV *ruv, ReplicaId rid, const char *replica_purl)
+{
+ RUVElement* replica;
+ int rc = RUV_NOTFOUND;
+
+ PR_ASSERT (ruv && replica_purl);
+
+ PR_RWLock_Wlock (ruv->lock);
+ replica = ruvGetReplica (ruv, rid);
+ if (replica != NULL)
+ {
+ slapi_ch_free((void **)&(replica->replica_purl));
+ replica->replica_purl = slapi_ch_strdup(replica_purl);
+ rc = RUV_SUCCESS;
+ }
+
+ PR_RWLock_Unlock (ruv->lock);
+ return rc;
+}
+
+int
+ruv_add_index_replica (RUV *ruv, ReplicaId rid, const char *replica_purl, int index)
+{
+ RUVElement* replica;
+
+ PR_ASSERT (ruv && replica_purl);
+
+ PR_RWLock_Wlock (ruv->lock);
+ replica = ruvGetReplica (ruv, rid);
+ if (replica == NULL)
+ {
+ replica = ruvAddIndexReplicaNoCSN (ruv, rid, replica_purl, index);
+ }
+
+ PR_RWLock_Unlock (ruv->lock);
+
+ if (replica)
+ return RUV_SUCCESS;
+ else
+ return RUV_MEMORY_ERROR;
+}
+
+
+PRBool
+ruv_contains_replica (const RUV *ruv, ReplicaId rid)
+{
+ RUVElement *replica;
+
+ if (ruv == NULL)
+ return PR_FALSE;
+
+ PR_RWLock_Rlock (ruv->lock);
+ replica = ruvGetReplica (ruv, rid);
+ PR_RWLock_Unlock (ruv->lock);
+
+ return replica != NULL;
+}
+
+
+
+
+#define GET_LARGEST_CSN 231
+#define GET_SMALLEST_CSN 232
+static int
+get_csn_internal(const RUV *ruv, ReplicaId rid, CSN **csn, int whichone)
+{
+ RUVElement *replica;
+ int return_value = RUV_SUCCESS;
+
+ if (ruv == NULL || csn == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_get_largest_csn_for_replica: NULL argument\n");
+ return_value = RUV_BAD_DATA;
+ }
+ else
+ {
+ *csn = NULL;
+ /* prevent element from being destroyed while we get its data */
+ PR_RWLock_Rlock (ruv->lock);
+
+ replica = ruvGetReplica (ruv, rid);
+ /* replica without min csn is treated as a non-existent replica */
+ if (replica == NULL || replica->min_csn == NULL)
+ {
+ return_value = RUV_NOTFOUND;
+ }
+ else
+ {
+ switch (whichone)
+ {
+ case GET_LARGEST_CSN:
+ *csn = replica->csn ? csn_dup (replica->csn) : NULL;
+ break;
+ case GET_SMALLEST_CSN:
+ *csn = replica->min_csn ? csn_dup (replica->min_csn) : NULL;
+ break;
+ default:
+ *csn = NULL;
+ }
+ }
+ PR_RWLock_Unlock (ruv->lock);
+ }
+ return return_value;
+}
+
+
+int
+ruv_get_largest_csn_for_replica(const RUV *ruv, ReplicaId rid, CSN **csn)
+{
+ return get_csn_internal(ruv, rid, csn, GET_LARGEST_CSN);
+}
+
+int
+ruv_get_smallest_csn_for_replica(const RUV *ruv, ReplicaId rid, CSN **csn)
+{
+ return get_csn_internal(ruv, rid, csn, GET_SMALLEST_CSN);
+}
+
+const char *
+ruv_get_purl_for_replica(const RUV *ruv, ReplicaId rid)
+{
+ RUVElement *replica;
+ const char *return_value = NULL;
+
+ PR_RWLock_Rlock (ruv->lock);
+
+ replica = ruvGetReplica (ruv, rid);
+ if (replica != NULL)
+ {
+ return_value = replica->replica_purl;
+ }
+
+ PR_RWLock_Unlock (ruv->lock);
+
+ return return_value;
+}
+
+
+static int
+set_min_csn_nolock(RUV *ruv, const CSN *min_csn, const char *replica_purl)
+{
+ int return_value;
+ ReplicaId rid = csn_get_replicaid (min_csn);
+ RUVElement *replica = ruvGetReplica (ruv, rid);
+ if (NULL == replica)
+ {
+ replica = ruvAddReplica (ruv, min_csn, replica_purl);
+ if (replica)
+ return_value = RUV_SUCCESS;
+ else
+ return_value = RUV_MEMORY_ERROR;
+ }
+ else
+ {
+ if (replica->min_csn == NULL || csn_compare (min_csn, replica->min_csn) < 0)
+ {
+ csn_free(&replica->min_csn);
+ replica->min_csn = csn_dup(min_csn);
+ }
+
+ return_value = RUV_SUCCESS;
+ }
+
+ return return_value;
+}
+
+static int
+set_max_csn_nolock(RUV *ruv, const CSN *max_csn, const char *replica_purl)
+{
+ int return_value;
+ ReplicaId rid = csn_get_replicaid (max_csn);
+ RUVElement *replica = ruvGetReplica (ruv, rid);
+ if (NULL == replica)
+ {
+ replica = ruvAddReplica (ruv, max_csn, replica_purl);
+ if (replica)
+ return_value = RUV_SUCCESS;
+ else
+ return_value = RUV_MEMORY_ERROR;
+ }
+ else
+ {
+ if (replica_purl && replica->replica_purl == NULL)
+ replica->replica_purl = slapi_ch_strdup (replica_purl);
+ csn_free(&replica->csn);
+ replica->csn = csn_dup(max_csn);
+ replica->last_modified = current_time();
+ return_value = RUV_SUCCESS;
+ }
+ return return_value;
+}
+
+int
+ruv_set_min_csn(RUV *ruv, const CSN *min_csn, const char *replica_purl)
+{
+ int return_value;
+ PR_RWLock_Wlock (ruv->lock);
+ return_value = set_min_csn_nolock(ruv, min_csn, replica_purl);
+ PR_RWLock_Unlock (ruv->lock);
+ return return_value;
+}
+
+
+int
+ruv_set_max_csn(RUV *ruv, const CSN *max_csn, const char *replica_purl)
+{
+ int return_value;
+ PR_RWLock_Wlock (ruv->lock);
+ return_value = set_max_csn_nolock(ruv, max_csn, replica_purl);
+ PR_RWLock_Unlock (ruv->lock);
+ return return_value;
+}
+
+int
+ruv_set_csns(RUV *ruv, const CSN *csn, const char *replica_purl)
+{
+ RUVElement *replica;
+ ReplicaId rid;
+ int return_value;
+
+ if (ruv == NULL || csn == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_set_csns: NULL argument\n");
+ return_value = RUV_BAD_DATA;
+ }
+ else
+ {
+ rid = csn_get_replicaid (csn);
+
+ /* prevent element from being destroyed while we get its data */
+ PR_RWLock_Wlock (ruv->lock);
+
+ replica = ruvGetReplica (ruv, rid);
+ if (replica == NULL) /* add new replica */
+ {
+ replica = ruvAddReplica (ruv, csn, replica_purl);
+ if (replica)
+ return_value = RUV_SUCCESS;
+ else
+ return_value = RUV_MEMORY_ERROR;
+ }
+ else
+ {
+ if (csn_compare (csn, replica->csn) > 0)
+ {
+ if (replica->csn != NULL)
+ {
+ csn_init_by_csn ( replica->csn, csn );
+ }
+ else
+ {
+ replica->csn = csn_dup(csn);
+ }
+ replica->last_modified = current_time();
+ if (replica_purl && (NULL == replica->replica_purl ||
+ strcmp(replica->replica_purl, replica_purl) != 0))
+ {
+ /* slapi_ch_free accepts NULL pointer */
+ slapi_ch_free((void **)&replica->replica_purl);
+
+ replica->replica_purl = slapi_ch_strdup(replica_purl);
+ }
+ }
+ /* XXXggood only need to worry about this if real min csn not committed to changelog yet */
+ if (csn_compare (csn, replica->min_csn) < 0)
+ {
+ csn_free(&replica->min_csn);
+ replica->min_csn = csn_dup(csn);
+ }
+ return_value = RUV_SUCCESS;
+ }
+
+ PR_RWLock_Unlock (ruv->lock);
+ }
+ return return_value;
+}
+
+/* This function, for each replica keeps the smallest CSN its seen so far.
+ Used for initial setup of changelog purge vector */
+
+int
+ruv_set_csns_keep_smallest(RUV *ruv, const CSN *csn)
+{
+ RUVElement *replica;
+ ReplicaId rid;
+ int return_value;
+
+ if (ruv == NULL || csn == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "ruv_set_csns_keep_smallest: NULL argument\n");
+ return_value = RUV_BAD_DATA;
+ }
+ else
+ {
+ rid = csn_get_replicaid (csn);
+
+ /* prevent element from being destroyed while we get its data */
+ PR_RWLock_Wlock (ruv->lock);
+
+ replica = ruvGetReplica (ruv, rid);
+ if (replica == NULL) /* add new replica */
+ {
+ replica = ruvAddReplica (ruv, csn, NULL);
+ if (replica)
+ return_value = RUV_SUCCESS;
+ else
+ return_value = RUV_MEMORY_ERROR;
+ }
+ else
+ {
+ if (csn_compare (csn, replica->csn) < 0)
+ {
+ csn_free(&replica->csn);
+ replica->csn = csn_dup(csn);
+ replica->last_modified = current_time();
+ }
+
+ return_value = RUV_SUCCESS;
+ }
+
+ PR_RWLock_Unlock (ruv->lock);
+ }
+ return return_value;
+}
+
+
+void
+ruv_set_replica_generation(RUV *ruv, const char *csnstr)
+{
+ if (NULL != csnstr && NULL != ruv)
+ {
+ PR_RWLock_Wlock (ruv->lock);
+
+ if (NULL != ruv->replGen)
+ {
+ slapi_ch_free((void **)&ruv->replGen);
+ }
+ ruv->replGen = slapi_ch_strdup(csnstr);
+
+ PR_RWLock_Unlock (ruv->lock);
+ }
+}
+
+
+char *
+ruv_get_replica_generation(const RUV *ruv)
+{
+ char *return_str = NULL;
+
+ PR_RWLock_Rlock (ruv->lock);
+
+ if (ruv != NULL && ruv->replGen != NULL)
+ {
+ return_str = slapi_ch_strdup(ruv->replGen);
+ }
+
+ PR_RWLock_Unlock (ruv->lock);
+
+ return return_str;
+}
+
+static PRBool
+ruv_covers_csn_internal(const RUV *ruv, const CSN *csn, PRBool strict)
+{
+ RUVElement *replica;
+ ReplicaId rid;
+ PRBool return_value;
+
+ if (ruv == NULL || csn == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_covers_csn: NULL argument\n");
+ return_value = PR_FALSE;
+ }
+ else
+ {
+ rid = csn_get_replicaid(csn);
+ replica = ruvGetReplica (ruv, rid);
+ if (replica == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_covers_csn: replica for id %d not found\n", rid);
+ return_value = PR_FALSE;
+ }
+ else
+ {
+ if (strict)
+ {
+ return_value = (csn_compare (csn, replica->csn) < 0);
+ }
+ else
+ {
+ return_value = (csn_compare (csn, replica->csn) <= 0);
+ }
+ }
+ }
+ return return_value;
+}
+
+PRBool
+ruv_covers_csn(const RUV *ruv, const CSN *csn)
+{
+ PRBool rc;
+
+ PR_RWLock_Rlock (ruv->lock);
+ rc = ruv_covers_csn_internal(ruv, csn, PR_FALSE);
+ PR_RWLock_Unlock (ruv->lock);
+
+ return rc;
+}
+
+PRBool
+ruv_covers_csn_strict(const RUV *ruv, const CSN *csn)
+{
+ PRBool rc;
+
+ PR_RWLock_Rlock (ruv->lock);
+ rc = ruv_covers_csn_internal(ruv, csn, PR_TRUE);
+ PR_RWLock_Unlock (ruv->lock);
+
+ return rc;
+}
+
+
+/*
+ * The function gets min{maxcsns of all ruv elements} if get_the_max=0,
+ * or max{maxcsns of all ruv elements} if get_the_max != 0.
+ */
+static int
+ruv_get_min_or_max_csn(const RUV *ruv, CSN **csn, int get_the_max)
+{
+ int return_value;
+
+ if (ruv == NULL || csn == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_get_min_or_max_csn: NULL argument\n");
+ return_value = RUV_BAD_DATA;
+ }
+ else
+ {
+ CSN *found = NULL;
+ RUVElement *replica;
+ int cookie;
+ PR_RWLock_Rlock (ruv->lock);
+ for (replica = dl_get_first (ruv->elements, &cookie); replica;
+ replica = dl_get_next (ruv->elements, &cookie))
+ {
+ /*
+ * Skip replica whose maxcsn is NULL otherwise
+ * the code will return different min_csn if
+ * the sequence of the replicas is altered.
+ *
+ * don't use READ_ONLY replicas for computing the value of
+ * "found", as they seem to have NULL csn and min_csn
+ */
+ if (replica->csn == NULL || replica->rid == READ_ONLY_REPLICA_ID)
+ {
+ continue;
+ }
+
+ if (found == NULL ||
+ (!get_the_max && csn_compare(found, replica->csn)>0) ||
+ ( get_the_max && csn_compare(found, replica->csn)<0))
+ {
+ found = replica->csn;
+ }
+ }
+ if (found == NULL)
+ {
+ *csn = NULL;
+ }
+ else
+ {
+ *csn = csn_dup (found);
+ }
+ PR_RWLock_Unlock (ruv->lock);
+ return_value = RUV_SUCCESS;
+ }
+ return return_value;
+}
+
+int
+ruv_get_max_csn(const RUV *ruv, CSN **csn)
+{
+ return ruv_get_min_or_max_csn(ruv, csn, 1 /* get the max */);
+}
+
+int
+ruv_get_min_csn(const RUV *ruv, CSN **csn)
+{
+ return ruv_get_min_or_max_csn(ruv, csn, 0 /* get the min */);
+}
+
+int
+ruv_enumerate_elements (const RUV *ruv, FNEnumRUV fn, void *arg)
+{
+ int cookie;
+ RUVElement *elem;
+ int rc = 0;
+ ruv_enum_data enum_data = {0};
+
+ if (ruv == NULL || fn == NULL)
+ {
+ /* ONREPL - log error */
+ return -1;
+ }
+
+ PR_RWLock_Rlock (ruv->lock);
+ for (elem = (RUVElement*)dl_get_first (ruv->elements, &cookie); elem;
+ elem = (RUVElement*)dl_get_next (ruv->elements, &cookie))
+ {
+ /* we only return elements that contains both minimal and maximal CSNs */
+ if (elem->csn && elem->min_csn)
+ {
+ enum_data.csn = elem->csn;
+ enum_data.min_csn = elem->min_csn;
+ rc = fn (&enum_data, arg);
+ if (rc != 0)
+ break;
+ }
+ }
+
+ PR_RWLock_Unlock (ruv->lock);
+
+ return rc;
+}
+
+/*
+ * Convert a replica update vector to a NULL-terminated array
+ * of bervals. The caller is responsible for freeing the bervals.
+ */
+int
+ruv_to_bervals(const RUV *ruv, struct berval ***bvals)
+{
+ struct berval **returned_bervals = NULL;
+ int return_value;
+ if (ruv == NULL || bvals == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_to_bervals: NULL argument\n");
+ return_value = RUV_BAD_DATA;
+ }
+ else
+ {
+ int count;
+ int i;
+ RUVElement *replica;
+ char csnStr1 [CSN_STRSIZE];
+ char csnStr2 [CSN_STRSIZE];
+ int cookie;
+ PR_RWLock_Rlock (ruv->lock);
+ count = dl_get_count (ruv->elements) + 2;
+ returned_bervals = (struct berval **)slapi_ch_malloc(sizeof(struct berval *) * count);
+ returned_bervals[count - 1] = NULL;
+ returned_bervals[0] = (struct berval *)slapi_ch_malloc(sizeof(struct berval));
+ returned_bervals[0]->bv_val = slapi_ch_malloc(strlen(prefix_replicageneration) + strlen(ruv->replGen) + 2);
+ sprintf(returned_bervals[0]->bv_val, "%s %s",
+ prefix_replicageneration, ruv->replGen);
+ returned_bervals[0]->bv_len = strlen(returned_bervals[0]->bv_val);
+ for (i = 1, replica = dl_get_first (ruv->elements, &cookie); replica;
+ i++, replica = dl_get_next (ruv->elements, &cookie))
+ {
+ returned_bervals[i] = (struct berval *)slapi_ch_malloc(sizeof(struct berval));
+ returned_bervals[i]->bv_val = slapi_ch_malloc(strlen(prefix_ruvcsn) + RIDSTR_SIZE +
+ ((replica->replica_purl == NULL) ? 0 : strlen(replica->replica_purl)) +
+ ((replica->min_csn == NULL) ? 0 : CSN_STRSIZE) +
+ ((replica->csn == NULL) ? 0 : CSN_STRSIZE) + 5);
+ sprintf(returned_bervals[i]->bv_val, "%s%d%s%s}%s%s%s%s",
+ prefix_ruvcsn, replica->rid,
+ replica->replica_purl == NULL ? "" : " ",
+ replica->replica_purl == NULL ? "" : replica->replica_purl,
+ replica->min_csn == NULL ? "" : " ",
+ replica->min_csn == NULL ? "" : csn_as_string (replica->min_csn, PR_FALSE, csnStr1),
+ replica->csn == NULL ? "" : " ",
+ replica->csn == NULL ? "" : csn_as_string (replica->csn, PR_FALSE, csnStr2));
+ returned_bervals[i]->bv_len = strlen(returned_bervals[i]->bv_val);
+ }
+ PR_RWLock_Unlock (ruv->lock);
+ return_value = RUV_SUCCESS;
+ *bvals = returned_bervals;
+ }
+ return return_value;
+}
+
+int
+ruv_to_smod(const RUV *ruv, Slapi_Mod *smod)
+{
+ int return_value;
+
+ if (ruv == NULL || smod == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_to_smod: NULL argument\n");
+ return_value = RUV_BAD_DATA;
+ }
+ else
+ {
+ struct berval val;
+ RUVElement *replica;
+ int cookie;
+ char csnStr1 [CSN_STRSIZE];
+ char csnStr2 [CSN_STRSIZE];
+#define B_SIZ 1024
+ char buf[B_SIZ];
+ PR_RWLock_Rlock (ruv->lock);
+ slapi_mod_init (smod, dl_get_count (ruv->elements) + 1);
+ slapi_mod_set_type (smod, type_ruvElement);
+ slapi_mod_set_operation (smod, LDAP_MOD_REPLACE | LDAP_MOD_BVALUES);
+ PR_snprintf(buf, B_SIZ, "%s %s", prefix_replicageneration, ruv->replGen);
+ val.bv_val = buf;
+ val.bv_len = strlen(buf);
+ slapi_mod_add_value(smod, &val);
+ for (replica = dl_get_first (ruv->elements, &cookie); replica;
+ replica = dl_get_next (ruv->elements, &cookie))
+ {
+
+ PR_snprintf(buf, B_SIZ, "%s%d%s%s}%s%s%s%s", prefix_ruvcsn, replica->rid,
+ replica->replica_purl == NULL ? "" : " ",
+ replica->replica_purl == NULL ? "" : replica->replica_purl,
+ replica->min_csn == NULL ? "" : " ",
+ replica->min_csn == NULL ? "" : csn_as_string (replica->min_csn, PR_FALSE, csnStr1),
+ replica->csn == NULL ? "" : " ",
+ replica->csn == NULL ? "" : csn_as_string (replica->csn, PR_FALSE, csnStr2));
+ val.bv_len = strlen(buf);
+ slapi_mod_add_value(smod, &val);
+ }
+ PR_RWLock_Unlock (ruv->lock);
+ return_value = RUV_SUCCESS;
+ }
+ return return_value;
+}
+
+int
+ruv_last_modified_to_smod(const RUV *ruv, Slapi_Mod *smod)
+{
+ int return_value;
+
+ if (ruv == NULL || smod == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_last_modified_to_smod: NULL argument\n");
+ return_value = RUV_BAD_DATA;
+ }
+ else
+ {
+ struct berval val;
+ RUVElement *replica;
+ int cookie;
+ char buf[B_SIZ];
+ PR_RWLock_Rlock (ruv->lock);
+ slapi_mod_init (smod, dl_get_count (ruv->elements));
+ slapi_mod_set_type (smod, type_ruvElementUpdatetime);
+ slapi_mod_set_operation (smod, LDAP_MOD_REPLACE | LDAP_MOD_BVALUES);
+ val.bv_val = buf;
+ for (replica = dl_get_first (ruv->elements, &cookie); replica;
+ replica = dl_get_next (ruv->elements, &cookie))
+ {
+ PR_snprintf(buf, B_SIZ, "%s%d%s%s} %08lx", prefix_ruvcsn, replica->rid,
+ replica->replica_purl == NULL ? "" : " ",
+ replica->replica_purl == NULL ? "" : replica->replica_purl,
+ replica->last_modified);
+ val.bv_len = strlen(buf);
+ slapi_mod_add_value(smod, &val);
+ }
+ PR_RWLock_Unlock (ruv->lock);
+ return_value = RUV_SUCCESS;
+ }
+ return return_value;
+}
+
+/*
+ * XXXggood do we need "ruv_covers_ruv_strict" ???? */
+PRBool
+ruv_covers_ruv(const RUV *covering_ruv, const RUV *covered_ruv)
+{
+ PRBool return_value = PR_TRUE;
+ RUVElement *replica;
+ int cookie;
+
+ /* compare replica generations first */
+ if (covering_ruv->replGen == NULL)
+ {
+ if (covered_ruv->replGen)
+ return PR_FALSE;
+ }
+ else
+ {
+ if (covered_ruv->replGen == NULL)
+ return PR_FALSE;
+ }
+
+ if (strcasecmp (covered_ruv->replGen, covering_ruv->replGen))
+ return PR_FALSE;
+
+ /* replica generation is the same, now compare element by element */
+ for (replica = dl_get_first (covered_ruv->elements, &cookie);
+ NULL != replica;
+ replica = dl_get_next (covered_ruv->elements, &cookie))
+ {
+ if (replica->csn &&
+ (ruv_covers_csn(covering_ruv, replica->csn) == PR_FALSE))
+ {
+ return_value = PR_FALSE;
+ /* Don't break here - may leave something referenced? */
+ }
+ }
+ return return_value;
+}
+
+PRInt32
+ruv_replica_count (const RUV *ruv)
+{
+ if (ruv == NULL)
+ return 0;
+ else
+ {
+ int count;
+
+ PR_RWLock_Rlock (ruv->lock);
+ count = dl_get_count (ruv->elements);
+ PR_RWLock_Unlock (ruv->lock);
+
+ return count;
+ }
+}
+
+/*
+ * Extract all the referral URL's from the RUV (but self URL),
+ * returning them in an array of strings, that
+ * the caller must free.
+ */
+char **
+ruv_get_referrals(const RUV *ruv)
+{
+ char **r= NULL;
+ int n;
+ const char *mypurl = multimaster_get_local_purl();
+
+ PR_RWLock_Rlock (ruv->lock);
+
+ n = ruv_replica_count(ruv);
+ if(n>0)
+ {
+ RUVElement *replica;
+ int cookie;
+ int i= 0;
+ r= (char**)slapi_ch_calloc(sizeof(char*),n+1);
+ for (replica = dl_get_first (ruv->elements, &cookie); replica;
+ replica = dl_get_next (ruv->elements, &cookie))
+ {
+ /* Add URL into referrals if doesn't match self URL */
+ if((replica->replica_purl!=NULL) &&
+ (slapi_utf8casecmp((unsigned char *)replica->replica_purl,
+ (unsigned char *)mypurl) != 0))
+ {
+ r[i]= slapi_ch_strdup(replica->replica_purl);
+ i++;
+ }
+ }
+ }
+
+ PR_RWLock_Unlock (ruv->lock);
+
+ return r; /* Caller must free this */
+}
+
+void
+ruv_dump(const RUV *ruv, char *ruv_name, PRFileDesc *prFile)
+{
+ RUVElement *replica;
+ int cookie;
+ char csnstr1[CSN_STRSIZE];
+ char csnstr2[CSN_STRSIZE];
+ char buff[RUVSTR_SIZE];
+ int len = sizeof (buff);
+
+ PR_ASSERT(NULL != ruv);
+
+ PR_RWLock_Rlock (ruv->lock);
+
+ PR_snprintf (buff, len, "%s: {replicageneration} %s\n",
+ ruv_name ? ruv_name : type_ruvElement,
+ ruv->replGen == NULL ? "" : ruv->replGen);
+
+ if (prFile)
+ {
+ slapi_write_buffer (prFile, buff, strlen(buff));
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, buff);
+ }
+ for (replica = dl_get_first (ruv->elements, &cookie); replica;
+ replica = dl_get_next (ruv->elements, &cookie))
+ {
+ /* prefix_ruvcsn = "{replica " */
+ PR_snprintf (buff, len, "%s: %s%d%s%s} %s %s\n",
+ ruv_name ? ruv_name : type_ruvElement,
+ prefix_ruvcsn, replica->rid,
+ replica->replica_purl == NULL ? "" : " ",
+ replica->replica_purl == NULL ? "" : replica->replica_purl,
+ csn_as_string(replica->min_csn, PR_FALSE, csnstr1),
+ csn_as_string(replica->csn, PR_FALSE, csnstr2));
+ if (strlen (csnstr1) > 0) {
+ PR_snprintf (buff + strlen(buff) - 1, len - strlen(buff), " %08lx\n",
+ replica->last_modified);
+ }
+ if (prFile)
+ {
+ slapi_write_buffer (prFile, buff, strlen(buff));
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, buff);
+ }
+ }
+
+ PR_RWLock_Unlock (ruv->lock);
+}
+
+/* this function notifies the ruv that there are operations in progress so that
+ they can be added to the pending list for the appropriate client. */
+int ruv_add_csn_inprogress (RUV *ruv, const CSN *csn)
+{
+ RUVElement* replica;
+ char csn_str[CSN_STRSIZE];
+ int rc = RUV_SUCCESS;
+
+ PR_ASSERT (ruv && csn);
+
+ /* locate ruvElement */
+ PR_RWLock_Wlock (ruv->lock);
+ replica = ruvGetReplica (ruv, csn_get_replicaid (csn));
+ if (replica == NULL)
+ {
+ replica = ruvAddReplicaNoCSN (ruv, csn_get_replicaid (csn), NULL/*purl*/);
+ if (replica == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_add_csn_inprogress: failed to add replica"
+ " that created csn %s\n", csn_as_string (csn, PR_FALSE, csn_str));
+ rc = RUV_MEMORY_ERROR;
+ goto done;
+ }
+ }
+
+ /* check first that this csn is not already covered by this RUV */
+ if (ruv_covers_csn_internal(ruv, csn, PR_FALSE))
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_add_csn_inprogress: "
+ "the csn %s has already be seen - ignoring\n",
+ csn_as_string (csn, PR_FALSE, csn_str));
+ rc = RUV_COVERS_CSN;
+ goto done;
+ }
+
+ rc = csnplInsert (replica->csnpl, csn);
+ if (rc == 1) /* we already seen this csn */
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_add_csn_inprogress: "
+ "the csn %s has already be seen - ignoring\n",
+ csn_as_string (csn, PR_FALSE, csn_str));
+ rc = RUV_COVERS_CSN;
+ }
+ else if(rc != 0)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_add_csn_inprogress: failed to insert csn %s"
+ " into pending list\n", csn_as_string (csn, PR_FALSE, csn_str));
+ rc = RUV_UNKNOWN_ERROR;
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_add_csn_inprogress: successfully inserted csn %s"
+ " into pending list\n", csn_as_string (csn, PR_FALSE, csn_str));
+ rc = RUV_SUCCESS;
+ }
+
+done:
+ PR_RWLock_Unlock (ruv->lock);
+ return rc;
+}
+
+int ruv_cancel_csn_inprogress (RUV *ruv, const CSN *csn)
+{
+ RUVElement* replica;
+ int rc = RUV_SUCCESS;
+
+ PR_ASSERT (ruv && csn);
+
+ /* locate ruvElement */
+ PR_RWLock_Wlock (ruv->lock);
+ replica = ruvGetReplica (ruv, csn_get_replicaid (csn));
+ if (replica == NULL)
+ {
+ /* ONREPL - log error */
+ rc = RUV_NOTFOUND;
+ goto done;
+ }
+
+ rc = csnplRemove (replica->csnpl, csn);
+ if (rc != 0)
+ rc = RUV_NOTFOUND;
+ else
+ rc = RUV_SUCCESS;
+
+done:
+ PR_RWLock_Unlock (ruv->lock);
+ return rc;
+}
+
+int ruv_update_ruv (RUV *ruv, const CSN *csn, const char *replica_purl, PRBool isLocal)
+{
+ int rc=RUV_SUCCESS;
+ char csn_str[CSN_STRSIZE];
+ CSN *max_csn;
+ CSN *first_csn = NULL;
+ RUVElement *replica;
+
+ PR_ASSERT (ruv && csn);
+
+ PR_RWLock_Wlock (ruv->lock);
+
+ replica = ruvGetReplica (ruv, csn_get_replicaid (csn));
+ if (replica == NULL)
+ {
+ /* we should have a ruv element at this point because it would have
+ been added by ruv_add_inprogress function */
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_update_ruv: "
+ "can't locate RUV element for replica %d\n", csn_get_replicaid (csn));
+ goto done;
+ }
+
+ if (csnplCommit(replica->csnpl, csn) != 0)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "ruv_update_ruv: cannot commit csn %s\n",
+ csn_as_string(csn, PR_FALSE, csn_str));
+ rc = RUV_CSNPL_ERROR;
+ goto done;
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_update_ruv: "
+ "successfully committed csn %s\n", csn_as_string(csn, PR_FALSE, csn_str));
+ }
+
+ if ((max_csn = csnplRollUp(replica->csnpl, &first_csn)) != NULL)
+ {
+#ifdef DEBUG
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_update_ruv: rolled up to csn %s\n",
+ csn_as_string(max_csn, PR_FALSE, csn_str)); /* XXXggood remove debugging */
+#endif
+ /* replica object sets min csn for local replica */
+ if (!isLocal && replica->min_csn == NULL) {
+ /* bug 559223 - it seems that, under huge stress, a server might pass
+ * through this code when more than 1 change has already been sent and commited into
+ * the pending lists... Therefore, as we are trying to set the min_csn ever
+ * generated by this replica, we need to set the first_csn as the min csn in the
+ * ruv */
+ set_min_csn_nolock(ruv, first_csn, replica_purl);
+ }
+ set_max_csn_nolock(ruv, max_csn, replica_purl);
+ /* It is possible that first_csn points to max_csn.
+ We need to free it once */
+ if (max_csn != first_csn) {
+ csn_free(&first_csn);
+ }
+ csn_free(&max_csn);
+ }
+
+done:
+ PR_RWLock_Unlock (ruv->lock);
+
+ return rc;
+}
+
+/* Helper functions */
+
+static int
+ruvInit (RUV **ruv, int initCount)
+{
+ PR_ASSERT (ruv);
+
+ /* allocate new RUV */
+ *ruv = (RUV *)slapi_ch_calloc (1, sizeof (RUV));
+ if (ruv == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "ruvInit: memory allocation failed\n");
+ return RUV_MEMORY_ERROR;
+ }
+
+ /* allocate elements */
+ (*ruv)->elements = dl_new ();
+ if ((*ruv)->elements == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "ruvInit: memory allocation failed\n");
+ return RUV_MEMORY_ERROR;
+ }
+
+ dl_init ((*ruv)->elements, initCount);
+
+ /* create lock */
+ (*ruv)->lock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "ruv_lock");
+ if ((*ruv)->lock == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "ruvInit: failed to create lock\n");
+ return RUV_NSPR_ERROR;
+ }
+
+ return RUV_SUCCESS;
+}
+
+static void
+ruvFreeReplica (void **data)
+{
+ RUVElement *element = *(RUVElement**)data;
+
+ if (NULL != element)
+ {
+ if (NULL != element->csn)
+ {
+ csn_free (&element->csn);
+ }
+ if (NULL != element->min_csn)
+ {
+ csn_free (&element->min_csn);
+ }
+ /* slapi_ch_free accepts NULL pointer */
+ slapi_ch_free((void **)&element->replica_purl);
+
+ if (element->csnpl)
+ {
+ csnplFree (&(element->csnpl));
+ }
+ slapi_ch_free ((void **)&element);
+ }
+}
+
+static RUVElement*
+ruvAddReplica (RUV *ruv, const CSN *csn, const char *replica_purl)
+{
+ RUVElement *replica;
+
+ PR_ASSERT (NULL != ruv);
+ PR_ASSERT (NULL != csn);
+
+ replica = (RUVElement *)slapi_ch_calloc (1, sizeof (RUVElement));
+ if (replica == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "ruvAddReplica: memory allocation failed\n");
+ return NULL;
+ }
+
+ replica->rid = csn_get_replicaid (csn);
+/* PR_ASSERT(replica->rid != READ_ONLY_REPLICA_ID); */
+
+ replica->csn = csn_dup (csn);
+ replica->last_modified = current_time();
+ replica->min_csn = csn_dup (csn);
+
+ replica->replica_purl = slapi_ch_strdup(replica_purl);
+ replica->csnpl = csnplNew ();
+
+ dl_add (ruv->elements, replica);
+
+ return replica;
+}
+
+static RUVElement*
+ruvAddReplicaNoCSN (RUV *ruv, ReplicaId rid, const char *replica_purl)
+{
+ RUVElement *replica;
+
+ PR_ASSERT (NULL != ruv);
+/* PR_ASSERT(rid != READ_ONLY_REPLICA_ID); */
+
+ replica = (RUVElement *)slapi_ch_calloc (1, sizeof (RUVElement));
+ if (replica == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "ruvAddReplicaNoCSN: memory allocation failed\n");
+ return NULL;
+ }
+
+ replica->rid = rid;
+ replica->replica_purl = slapi_ch_strdup(replica_purl);
+ replica->csnpl = csnplNew ();
+
+ dl_add (ruv->elements, replica);
+
+ return replica;
+}
+
+static RUVElement*
+ruvAddIndexReplicaNoCSN (RUV *ruv, ReplicaId rid, const char *replica_purl, int index)
+{
+ RUVElement *replica;
+
+ PR_ASSERT (NULL != ruv);
+/* PR_ASSERT(rid != READ_ONLY_REPLICA_ID); */
+
+ replica = (RUVElement *)slapi_ch_calloc (1, sizeof (RUVElement));
+ if (replica == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "ruvAddIndexReplicaNoCSN: memory allocation failed\n");
+ return NULL;
+ }
+
+ replica->rid = rid;
+ replica->replica_purl = slapi_ch_strdup(replica_purl);
+ replica->csnpl = csnplNew ();
+
+ dl_add_index (ruv->elements, replica, index);
+
+ return replica;
+}
+
+static RUVElement *
+ruvGetReplica (const RUV *ruv, ReplicaId rid)
+{
+ PR_ASSERT (ruv /* && rid >= 0 -- rid can't be negative */);
+
+ return (RUVElement *)dl_get (ruv->elements, (const void*)&rid, ruvReplicaCompare);
+}
+
+static int
+ruvReplicaCompare (const void *el1, const void *el2)
+{
+ RUVElement *replica = (RUVElement*)el1;
+ ReplicaId *rid1 = (ReplicaId*) el2;
+
+ if (replica == NULL || rid1 == NULL)
+ return -1;
+
+ if (*rid1 == replica->rid)
+ return 0;
+
+ if (*rid1 < replica->rid)
+ return -1;
+ else
+ return 1;
+}
+
+
+
+/*
+ * Given a berval that points to a string of the form:
+ * "{dbgen} generation-id", return a copy of the
+ * "generation-id" part in a null-terminated string.
+ * Returns NULL if the berval is malformed.
+ */
+static char *
+get_replgen_from_berval(const struct berval *bval)
+{
+ char *ret_string = NULL;
+
+ if (NULL != bval && NULL != bval->bv_val &&
+ (bval->bv_len > strlen(prefix_replicageneration)) &&
+ strncasecmp(bval->bv_val, prefix_replicageneration,
+ strlen(prefix_replicageneration)) == 0)
+ {
+ unsigned int index = strlen(prefix_replicageneration);
+ /* Skip any whitespace */
+ while (index++ < bval->bv_len && bval->bv_val[index] == ' ');
+ if (index < bval->bv_len)
+ {
+ unsigned int ret_len = bval->bv_len - index;
+ ret_string = slapi_ch_malloc(ret_len + 1);
+ memcpy(ret_string, &bval->bv_val[index], ret_len);
+ ret_string[ret_len] = '\0';
+ }
+ }
+ return ret_string;
+}
+
+
+
+/*
+ * Given a berval that points to a string of the form:
+ * "{replica ldap[s]//host:port} <min_csn> <csn>", parse out the
+ * partial URL and the CSNs into an RUVElement, and return
+ * a pointer to the copy. Returns NULL if the berval is
+ * malformed.
+ */
+static RUVElement *
+get_ruvelement_from_berval(const struct berval *bval)
+{
+ RUVElement *ret_ruve = NULL;
+ char *purl = NULL;
+ ReplicaId rid = 0;
+ char ridbuff [RIDSTR_SIZE];
+ int i;
+
+ if (NULL != bval && NULL != bval->bv_val &&
+ bval->bv_len > strlen(prefix_ruvcsn) &&
+ strncasecmp(bval->bv_val, prefix_ruvcsn, strlen(prefix_ruvcsn)) == 0)
+ {
+ unsigned int urlbegin = strlen(prefix_ruvcsn);
+ unsigned int urlend;
+ unsigned int mincsnbegin;
+
+ /* replica id must be here */
+ i = 0;
+ while (isdigit (bval->bv_val[urlbegin]))
+ {
+ ridbuff [i] = bval->bv_val[urlbegin];
+ i++;
+ urlbegin ++;
+ }
+
+ if (i == 0) /* replicaid is missing */
+ goto loser;
+
+ ridbuff[i] = '\0';
+ rid = atoi (ridbuff);
+
+ if (bval->bv_val[urlbegin] == '}')
+ {
+ /* No purl in this value */
+ purl = NULL;
+ mincsnbegin = urlbegin + 1;
+ }
+ else
+ {
+ while (urlbegin++ < bval->bv_len && bval->bv_val[urlbegin] == ' ');
+ urlend = urlbegin;
+ while (urlend++ < bval->bv_len && bval->bv_val[urlend] != '}');
+ purl = slapi_ch_malloc(urlend - urlbegin + 1);
+ memcpy(purl, &bval->bv_val[urlbegin], urlend - urlbegin);
+ purl[urlend - urlbegin] = '\0';
+ mincsnbegin = urlend;
+ }
+ /* Skip any whitespace before the first (minimum) CSN */
+ while (mincsnbegin++ < (bval->bv_len-1) && bval->bv_val[mincsnbegin] == ' ');
+ /* Now, mincsnbegin should contain the index of the beginning of the first csn */
+ if (mincsnbegin >= bval->bv_len)
+ {
+ /* Missing the entire content*/
+ if (purl == NULL)
+ goto loser;
+ else /* we have just purl - no changes from the replica has been seen */
+ {
+ ret_ruve = (RUVElement *)slapi_ch_calloc(1, sizeof(RUVElement));
+ ret_ruve->rid = rid;
+ ret_ruve->replica_purl = purl;
+ }
+ }
+ else
+ {
+ if (bval->bv_len - mincsnbegin != (_CSN_VALIDCSN_STRLEN * 2) + 1)
+ {
+ /* Malformed - incorrect length for 2 CSNs + space */
+ goto loser;
+ }
+ else
+ {
+ char mincsnstr[CSN_STRSIZE];
+ char maxcsnstr[CSN_STRSIZE];
+
+ memset(mincsnstr, '\0', CSN_STRSIZE);
+ memset(maxcsnstr, '\0', CSN_STRSIZE);
+ memcpy(mincsnstr, &bval->bv_val[mincsnbegin], _CSN_VALIDCSN_STRLEN);
+ memcpy(maxcsnstr, &bval->bv_val[mincsnbegin + _CSN_VALIDCSN_STRLEN + 1], _CSN_VALIDCSN_STRLEN);
+ ret_ruve = (RUVElement *)slapi_ch_calloc(1, sizeof(RUVElement));
+ ret_ruve->min_csn = csn_new_by_string(mincsnstr);
+ ret_ruve->csn = csn_new_by_string(maxcsnstr);
+ ret_ruve->rid = rid;
+ ret_ruve->replica_purl = purl;
+ if (NULL == ret_ruve->min_csn || NULL == ret_ruve->csn)
+ {
+ goto loser;
+ }
+ }
+ }
+ }
+
+ /* initialize csn pending list */
+ ret_ruve->csnpl = csnplNew ();
+ if (ret_ruve->csnpl == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "get_ruvelement_from_berval: failed to create csn pending list\n");
+ goto loser;
+ }
+
+ return ret_ruve;
+
+loser:
+ /* slapi_ch_free accepts NULL pointer */
+ slapi_ch_free((void **)&purl);
+ if (NULL != ret_ruve)
+ {
+ if (NULL != ret_ruve->min_csn)
+ {
+ csn_free(&ret_ruve->min_csn);
+ }
+ if (NULL != ret_ruve->csn)
+ {
+ csn_free(&ret_ruve->csn);
+ }
+ slapi_ch_free((void **)&ret_ruve);
+ }
+ return NULL;
+}
+
+int
+ruv_move_local_supplier_to_first(RUV *ruv, ReplicaId aRid)
+{
+ RUVElement * elem = NULL;
+ int rc = RUV_NOTFOUND;
+
+ PR_ASSERT(ruv);
+
+ PR_RWLock_Wlock (ruv->lock);
+
+ elem = (RUVElement *)dl_delete(ruv->elements,(const void*)&aRid, ruvReplicaCompare, 0);
+ if (elem) {
+ dl_add_index(ruv->elements, elem, 1);
+ rc = RUV_SUCCESS;
+ }
+
+ PR_RWLock_Unlock (ruv->lock);
+
+ return rc;
+}
+
+
+int
+ruv_get_first_id_and_purl(RUV *ruv, ReplicaId *rid, char **replica_purl )
+{
+ RUVElement * first = NULL;
+ int cookie;
+ int rc;
+
+ PR_ASSERT(ruv);
+
+ PR_RWLock_Rlock (ruv->lock);
+ first = dl_get_first(ruv->elements, &cookie);
+ if ( first == NULL )
+ {
+ rc = RUV_MEMORY_ERROR;
+ }
+ else
+ {
+ *rid = first->rid;
+ *replica_purl = first->replica_purl;
+ rc = RUV_SUCCESS;
+ }
+ PR_RWLock_Unlock (ruv->lock);
+ return rc;
+}
+
+int ruv_local_contains_supplier(RUV *ruv, ReplicaId rid)
+{
+ int cookie;
+ RUVElement *elem = NULL;
+
+ PR_ASSERT(ruv);
+
+ PR_RWLock_Rlock (ruv->lock);
+ for (elem = dl_get_first (ruv->elements, &cookie);
+ elem;
+ elem = dl_get_next (ruv->elements, &cookie))
+ {
+ if (elem->rid == rid){
+ PR_RWLock_Unlock (ruv->lock);
+ return 1;
+ }
+ }
+ PR_RWLock_Unlock (ruv->lock);
+ return 0;
+}
+
+PRBool ruv_has_csns(const RUV *ruv)
+{
+ PRBool retval = PR_TRUE;
+ CSN *mincsn = NULL;
+ CSN *maxcsn = NULL;
+
+ ruv_get_min_csn(ruv, &mincsn);
+ ruv_get_max_csn(ruv, &maxcsn);
+ if (mincsn) {
+ csn_free(&mincsn);
+ csn_free(&maxcsn);
+ } else if (maxcsn) {
+ csn_free(&maxcsn);
+ } else {
+ retval = PR_FALSE; /* both min and max are false */
+ }
+
+ return retval;
+}
+
+/* Check if the first ruv is newer than the second one */
+PRBool
+ruv_is_newer (Object *sruvobj, Object *cruvobj)
+{
+ RUV *sruv, *cruv;
+ RUVElement *sreplica, *creplica;
+ int scookie, ccookie;
+ int is_newer = PR_FALSE;
+
+ if ( sruvobj == NULL ) {
+ return 0;
+ }
+ if ( cruvobj == NULL ) {
+ return 1;
+ }
+ sruv = (RUV *) object_get_data ( sruvobj );
+ cruv = (RUV *) object_get_data ( cruvobj );
+
+ for (sreplica = dl_get_first (sruv->elements, &scookie); sreplica;
+ sreplica = dl_get_next (sruv->elements, &scookie))
+ {
+ /* A hub may have a dummy ruv with rid 65535 */
+ if ( sreplica->csn == NULL ) continue;
+
+ for (creplica = dl_get_first (cruv->elements, &ccookie); creplica;
+ creplica = dl_get_next (cruv->elements, &ccookie))
+ {
+ if ( sreplica->rid == creplica->rid ) {
+ if ( csn_compare ( sreplica->csn, creplica->csn ) > 0 ) {
+ is_newer = PR_TRUE;
+ }
+ break;
+ }
+ }
+ if ( creplica == NULL || is_newer ) {
+ is_newer = PR_TRUE;
+ break;
+ }
+ }
+
+ return is_newer;
+}
+
+#ifdef TESTING /* Some unit tests for code in this file */
+
+static void
+ruv_dump_internal(RUV *ruv)
+{
+ RUVElement *replica;
+ int cookie;
+ char csnstr1[CSN_STRSIZE];
+ char csnstr2[CSN_STRSIZE];
+
+ PR_ASSERT(NULL != ruv);
+ printf("{replicageneration} %s\n", ruv->replGen == NULL ? "NULL" : ruv->replGen);
+ for (replica = dl_get_first (ruv->elements, &cookie); replica;
+ replica = dl_get_next (ruv->elements, &cookie))
+ {
+ printf("{replica%s%s} %s %s\n",
+ replica->replica_purl == NULL ? "" : " ",
+ replica->replica_purl == NULL ? "" : replica->replica_purl,
+ csn_as_string(replica->min_csn, PR_FALSE, csnstr1),
+ csn_as_string(replica->csn, PR_FALSE, csnstr2));
+ }
+}
+
+void
+ruv_test()
+{
+ const struct berval *vals[5];
+ struct berval val0, val1, val2, val3;
+ RUV *ruv;
+ Slapi_Attr *attr;
+ Slapi_Value *sv0, *sv1, *sv2, *sv3;
+ int rc;
+ char csnstr[CSN_STRSIZE];
+ char *gen;
+ CSN *newcsn;
+ ReplicaId *ids;
+ int nids;
+ Slapi_Mod smods;
+ PRBool covers;
+
+ vals[0] = &val0;
+ vals[1] = &val1;
+ vals[2] = &val2;
+ vals[3] = &val3;
+ vals[4] = NULL;
+
+ val0.bv_val = "{replicageneration} 0440FDC0A33F";
+ val0.bv_len = strlen(val0.bv_val);
+
+ val1.bv_val = "{replica ldap://ggood.mcom.com:389} 12345670000000FE0000 12345671000000FE0000";
+ val1.bv_len = strlen(val1.bv_val);
+
+ val2.bv_val = "{replica ldaps://an-impossibly-long-host-name-that-drags-on-forever-and-forever.mcom.com:389} 11112110000000FF0000 11112111000000FF0000";
+ val2.bv_len = strlen(val2.bv_val);
+
+ val3.bv_val = "{replica} 12345672000000FD0000 12345673000000FD0000";
+ val3.bv_len = strlen(val3.bv_val);
+
+ rc = ruv_init_from_bervals(vals, &ruv);
+ ruv_dump_internal(ruv);
+
+ attr = slapi_attr_new();
+ attr = slapi_attr_init(attr, "ruvelement");
+ sv0 = slapi_value_new();
+ sv1 = slapi_value_new();
+ sv2 = slapi_value_new();
+ sv3 = slapi_value_new();
+ slapi_value_init_berval(sv0, &val0);
+ slapi_value_init_berval(sv1, &val1);
+ slapi_value_init_berval(sv2, &val2);
+ slapi_value_init_berval(sv3, &val3);
+ slapi_attr_add_value(attr, sv0);
+ slapi_attr_add_value(attr, sv1);
+ slapi_attr_add_value(attr, sv2);
+ slapi_attr_add_value(attr, sv3);
+ rc = ruv_init_from_slapi_attr(attr, &ruv);
+ ruv_dump_internal(ruv);
+
+ rc = ruv_delete_replica(ruv, 0xFF);
+ /* Should delete one replica */
+ ruv_dump_internal(ruv);
+
+ rc = ruv_delete_replica(ruv, 0xAA);
+ /* No such replica - should not do anything */
+ ruv_dump_internal(ruv);
+
+ rc = ruv_get_largest_csn_for_replica(ruv, 0xFE, &newcsn);
+ if (NULL != newcsn)
+ {
+ csn_as_string(newcsn, PR_FALSE, csnstr);
+ printf("Replica 0x%X has largest csn \"%s\"\n", 0xFE, csnstr);
+ }
+ else
+ {
+ printf("BAD - can't get largest CSN for replica 0x%X\n", 0xFE);
+ }
+
+ rc = ruv_get_smallest_csn_for_replica(ruv, 0xFE, &newcsn);
+ if (NULL != newcsn)
+ {
+ csn_as_string(newcsn, PR_FALSE, csnstr);
+ printf("Replica 0x%X has smallest csn \"%s\"\n", 0xFE, csnstr);
+ }
+ else
+ {
+ printf("BAD - can't get smallest CSN for replica 0x%X\n", 0xFE);
+ }
+ rc = ruv_get_largest_csn_for_replica(ruv, 0xAA, &newcsn);
+ printf("ruv_get_largest_csn_for_replica on non-existent replica ID returns %d\n", rc);
+
+ rc = ruv_get_smallest_csn_for_replica(ruv, 0xAA, &newcsn);
+ printf("ruv_get_smallest_csn_for_replica on non-existent replica ID returns %d\n", rc);
+
+ newcsn = csn_new_by_string("12345674000000FE0000"); /* Old replica 0xFE */
+ rc = ruv_set_csns(ruv, newcsn, "ldaps://foobar.mcom.com");
+ /* Should update replica FE's CSN */
+ ruv_dump_internal(ruv);
+
+ newcsn = csn_new_by_string("12345675000000FB0000"); /* New replica 0xFB */
+ rc = ruv_set_csns(ruv, newcsn, "ldaps://foobar.mcom.com");
+ /* Should get a new replica in the list with min == max csn */
+ ruv_dump_internal(ruv);
+
+ newcsn = csn_new_by_string("12345676000000FD0000"); /* Old replica 0xFD */
+ rc = ruv_set_csns(ruv, newcsn, "ldaps://foobar.mcom.com");
+ /* Should update replica 0xFD so new CSN is newer than min CSN */
+ ruv_dump_internal(ruv);
+
+ gen = ruv_get_replica_generation(ruv);
+ printf("replica generation is \"%s\"\n", gen);
+
+ newcsn = csn_new_by_string("12345673000000FE0000"); /* Old replica 0xFE */
+ covers = ruv_covers_csn(ruv, newcsn); /* should say "true" */
+
+ newcsn = csn_new_by_string("12345675000000FE0000"); /* Old replica 0xFE */
+ covers = ruv_covers_csn(ruv, newcsn); /* Should say "false" */
+
+ newcsn = csn_new_by_string("123456700000000A0000"); /* New replica 0A */
+ rc = ruv_set_min_csn(ruv, newcsn, "ldap://repl0a.mcom.com");
+ ruv_dump_internal(ruv);
+
+ newcsn = csn_new_by_string("123456710000000A0000"); /* New replica 0A */
+ rc = ruv_set_max_csn(ruv, newcsn, "ldap://repl0a.mcom.com");
+ ruv_dump_internal(ruv);
+
+ newcsn = csn_new_by_string("123456700000000B0000"); /* New replica 0B */
+ rc = ruv_set_max_csn(ruv, newcsn, "ldap://repl0b.mcom.com");
+ ruv_dump_internal(ruv);
+
+ newcsn = csn_new_by_string("123456710000000B0000"); /* New replica 0B */
+ rc = ruv_set_min_csn(ruv, newcsn, "ldap://repl0b.mcom.com");
+ ruv_dump_internal(ruv);
+
+ /* ONREPL test ruv enumeration */
+
+ rc = ruv_to_smod(ruv, &smods);
+
+ ruv_destroy(&ruv);
+}
+#endif /* TESTING */
diff --git a/ldap/servers/plugins/replication/repl5_ruv.h b/ldap/servers/plugins/replication/repl5_ruv.h
new file mode 100644
index 00000000..8484e74b
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl5_ruv.h
@@ -0,0 +1,88 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+/* repl5_ruv.h - interface for replica update vector */
+
+#ifndef REPL5_RUV
+#define REPL5_RUV
+
+#include "slapi-private.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct _ruv RUV;
+
+enum
+{
+ RUV_SUCCESS=0,
+ RUV_BAD_DATA,
+ RUV_NOTFOUND,
+ RUV_MEMORY_ERROR,
+ RUV_NSPR_ERROR,
+ RUV_BAD_FORMAT,
+ RUV_UNKNOWN_ERROR,
+ RUV_ALREADY_EXIST,
+ RUV_CSNPL_ERROR,
+ RUV_COVERS_CSN
+};
+
+typedef struct ruv_enum_data
+{
+ CSN *csn;
+ CSN *min_csn;
+} ruv_enum_data;
+
+typedef int (*FNEnumRUV) (const ruv_enum_data *element, void *arg);
+int ruv_init_new (const char *replGen, ReplicaId rid, const char *purl, RUV **ruv);
+int ruv_init_from_bervals(struct berval** vals, RUV **ruv);
+int ruv_init_from_slapi_attr(Slapi_Attr *attr, RUV **ruv);
+int ruv_init_from_slapi_attr_and_check_purl(Slapi_Attr *attr, RUV **ruv, ReplicaId *rid);
+RUV* ruv_dup (const RUV *ruv);
+void ruv_destroy (RUV **ruv);
+void ruv_copy_and_destroy (RUV **srcruv, RUV **destruv);
+int ruv_replace_replica_purl (RUV *ruv, ReplicaId rid, const char *replica_purl);
+int ruv_delete_replica (RUV *ruv, ReplicaId rid);
+int ruv_add_replica (RUV *ruv, ReplicaId rid, const char *replica_purl);
+int ruv_add_index_replica (RUV *ruv, ReplicaId rid, const char *replica_purl, int index);
+PRBool ruv_contains_replica (const RUV *ruv, ReplicaId rid);
+int ruv_get_largest_csn_for_replica(const RUV *ruv, ReplicaId rid, CSN **csn);
+int ruv_get_smallest_csn_for_replica(const RUV *ruv, ReplicaId rid, CSN **csn);
+int ruv_set_csns(RUV *ruv, const CSN *csn, const char *replica_purl);
+int ruv_set_csns_keep_smallest(RUV *ruv, const CSN *csn);
+int ruv_set_max_csn(RUV *ruv, const CSN *max_csn, const char *replica_purl);
+int ruv_set_min_csn(RUV *ruv, const CSN *min_csn, const char *replica_purl);
+const char *ruv_get_purl_for_replica(const RUV *ruv, ReplicaId rid);
+char *ruv_get_replica_generation (const RUV *ruv);
+void ruv_set_replica_generation (RUV *ruv, const char *generation);
+PRBool ruv_covers_ruv(const RUV *covering_ruv, const RUV *covered_ruv);
+PRBool ruv_covers_csn(const RUV *ruv, const CSN *csn);
+PRBool ruv_covers_csn_strict(const RUV *ruv, const CSN *csn);
+int ruv_get_min_csn(const RUV *ruv, CSN **csn);
+int ruv_get_max_csn(const RUV *ruv, CSN **csn);
+int ruv_enumerate_elements (const RUV *ruv, FNEnumRUV fn, void *arg);
+int ruv_to_smod(const RUV *ruv, Slapi_Mod *smod);
+int ruv_last_modified_to_smod(const RUV *ruv, Slapi_Mod *smod);
+int ruv_to_bervals(const RUV *ruv, struct berval ***bvals);
+PRInt32 ruv_replica_count (const RUV *ruv);
+char **ruv_get_referrals(const RUV *ruv);
+void ruv_dump(const RUV *ruv, char *ruv_name, PRFileDesc *prFile);
+int ruv_add_csn_inprogress (RUV *ruv, const CSN *csn);
+int ruv_cancel_csn_inprogress (RUV *ruv, const CSN *csn);
+int ruv_update_ruv (RUV *ruv, const CSN *csn, const char *replica_purl, PRBool isLocal);
+int ruv_move_local_supplier_to_first(RUV *ruv, ReplicaId rid);
+int ruv_get_first_id_and_purl(RUV *ruv, ReplicaId *rid, char **replica_purl );
+int ruv_local_contains_supplier(RUV *ruv, ReplicaId rid);
+/* returns true if the ruv has any csns, false otherwise - used for testing
+ whether or not an RUV is empty */
+PRBool ruv_has_csns(const RUV *ruv);
+PRBool ruv_is_newer (Object *sruv, Object *cruv);
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/ldap/servers/plugins/replication/repl5_schedule.c b/ldap/servers/plugins/replication/repl5_schedule.c
new file mode 100644
index 00000000..427e79a9
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl5_schedule.c
@@ -0,0 +1,742 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* repl5_schedule.c */
+/*
+
+The schedule object implements the scheduling policy for a DS 5.0 replication
+supplier.
+
+Methods:
+schedule_set() - sets the schedule
+schedule_get() - gets the schedule
+schedule_in_window_now() - returns TRUE if a replication session
+ should commence.
+schedule_next() - returns the next time that replication is
+ scheduled to occur.
+schedule_notify() - called to inform the scheduler when entries
+ have been updated.
+schedule_set_priority_attributes() - sets the attributes that are
+ considered "high priority". A modification to one of these attributes
+ will cause replication to commence asap, overriding the startup
+ delay and maximum backlog. Also includes an additional parameter
+ that controls whether priority attributes are propagated regardless
+ of the scheduling window, e.g. it's possible to configure things
+ so that password changes get propagated even if we're not in a
+ replication window.
+schedule_set_startup_delay() - sets the time that replication should
+ wait before commencing replication sessions.
+schedule_set_maximum_backlog() - sets the maximum number of updates
+ which can occur before replication will commence. If the backlog
+ threshhold is exceeded, then replication will commence ASAP,
+ overriding the startup delay.
+
+*/
+
+/* ONREPL - I made a simplifying assumption that a schedule item does not
+ cross day boundaries. Implementing this is hard because we search
+ for the items for a particular day only based on the item's staring time.
+ For instance if the current time is tuesday morning, we would not consider
+ the item that started on monday and continued through tuesday.
+ To simulate an item that crosses day boundaries, you can create 2 items -
+ one for the time in the first day and one for the time in the second.
+ We could do this internally by allowing items do span 2 days and
+ splitting them ourselves. This, however, is not currently implemented */
+
+#include "slapi-plugin.h"
+#include "repl5.h"
+
+#include <ctype.h> /* For isdigit() */
+
+/* from proto-slap.h */
+char *get_timestring(time_t *t);
+void free_timestring(char *timestr);
+
+typedef struct schedule_item {
+ struct schedule_item *next;
+ PRUint32 start; /* Start time, given as seconds after midnight */
+ PRUint32 end; /* End time */
+ unsigned char dow; /* Days of week, LSB = Sunday */
+} schedule_item;
+
+typedef struct schedule {
+ const char *session_id;
+ size_t max_backlog;
+ size_t startup_delay;
+ schedule_item *schedule_list; /* Linked list of schedule windows */
+ char **prio_attrs; /* Priority attributes - start replication now */
+ int prio_attrs_override_schedule;
+ PRTime last_session_end;
+ int last_session_status;
+ PRTime last_successful_session_end;
+ window_state_change_callback callback_fn; /* function to call when window opens/closes */
+ void *callback_arg; /* argument to pass to the window state change callback */
+ Slapi_Eq_Context pending_event; /* event scheduled with the event queue */
+ PRLock *lock;
+} schedule;
+
+/* Forward declarations */
+static schedule_item *parse_schedule_value(const Slapi_Value *v);
+static void schedule_window_state_change_event (Schedule *sch);
+static void unschedule_window_state_change_event (Schedule *sch);
+static void window_state_changed (time_t when, void *arg);
+static int schedule_in_window_now_nolock(Schedule *sch);
+static schedule_item* get_current_schedule_item (Schedule *sch);
+static time_t PRTime2time_t (PRTime tm);
+static PRTime schedule_next_nolock (Schedule *sch, PRBool start);
+static void free_schedule_list(schedule_item **schedule_list);
+
+#define SECONDS_PER_MINUTE 60
+#define SECONDS_PER_HOUR (60 * SECONDS_PER_MINUTE)
+#define SECONDS_PER_DAY (24 * SECONDS_PER_HOUR)
+#define DAYS_PER_WEEK 7
+#define ALL_DAYS 0x7F /* Bit mask */
+
+
+
+/*
+ * Create a new schedule object and return a pointer to it.
+ */
+Schedule*
+schedule_new(window_state_change_callback callback_fn, void *callback_arg, const char *session_id)
+{
+ Schedule *sch = NULL;
+ sch = (Schedule *)slapi_ch_calloc(1, sizeof(struct schedule));
+
+ sch->session_id = session_id ? session_id : "";
+ sch->callback_fn = callback_fn;
+ sch->callback_arg = callback_arg;
+
+ if ((sch->lock = PR_NewLock()) == NULL)
+ {
+ slapi_ch_free((void **)&sch);
+ }
+
+ return sch;
+}
+
+
+void
+schedule_destroy(Schedule *s)
+{
+ int i;
+
+ /* unschedule update window event if exists */
+ unschedule_window_state_change_event (s);
+
+ if (s->schedule_list)
+ {
+ free_schedule_list (&s->schedule_list);
+ }
+
+ if (NULL != s->prio_attrs)
+ {
+ for (i = 0; NULL != s->prio_attrs[i]; i++)
+ {
+ slapi_ch_free((void **)&(s->prio_attrs[i]));
+ }
+ slapi_ch_free((void **)&(s->prio_attrs));
+ }
+ PR_DestroyLock(s->lock);
+ s->lock = NULL;
+ slapi_ch_free((void **)&s);
+}
+
+static void
+free_schedule_list(schedule_item **schedule_list)
+{
+ schedule_item *si = *schedule_list;
+ schedule_item *tmp_si;
+ while (NULL != si)
+ {
+ tmp_si = si->next;
+ slapi_ch_free((void **)&si);
+ si = tmp_si;
+ }
+ *schedule_list = NULL;
+}
+
+
+
+/*
+ * Sets the schedule. Returns 0 if all of the schedule lines were
+ * correctly parsed and the new schedule has been put into effect.
+ * Returns -1 if one or more of the schedule items could not be
+ * parsed. If -1 is returned, then no changes have been made to the
+ * current schedule.
+ */
+int
+schedule_set(Schedule *sch, Slapi_Attr *attr)
+{
+ int return_value;
+ schedule_item *si = NULL;
+ schedule_item *new_schedule_list = NULL;
+ int valid = 1;
+
+ if (NULL != attr)
+ {
+ int ind;
+ Slapi_Value *sval;
+ ind = slapi_attr_first_value(attr, &sval);
+ while (ind >= 0)
+ {
+ si = parse_schedule_value(sval);
+ if (NULL == si)
+ {
+ valid = 0;
+ break;
+ }
+ /* Put at head of linked list */
+ si->next = new_schedule_list;
+ new_schedule_list = si;
+ ind = slapi_attr_next_value(attr, ind, &sval);
+ }
+ }
+
+ if (!valid)
+ {
+ /* deallocate any new schedule items */
+ free_schedule_list(&new_schedule_list);
+ return_value = -1;
+ }
+ else
+ {
+ PR_Lock(sch->lock);
+
+ /* if there is an update window event scheduled - unschedule it */
+ unschedule_window_state_change_event (sch);
+
+ free_schedule_list(&sch->schedule_list);
+ sch->schedule_list = new_schedule_list;
+
+ /* schedule an event to notify the caller about openning/closing of the update window */
+ schedule_window_state_change_event (sch);
+
+ PR_Unlock(sch->lock);
+ return_value = 0;
+ }
+ return return_value;
+}
+
+
+
+/*
+ * Returns the schedule.
+ */
+char **
+schedule_get(Schedule *sch)
+{
+ char **return_value = NULL;
+
+ return return_value;
+}
+
+
+
+/*
+ * Return an integer corresponding to the day of the week for
+ * "when".
+ */
+static PRInt32
+day_of_week(PRTime when)
+{
+
+ PRExplodedTime exp;
+
+ PR_ExplodeTime(when, PR_LocalTimeParameters, &exp);
+ return(exp.tm_wday);
+}
+
+
+/*
+ * Return the number of seconds between "when" and the
+ * most recent midnight.
+ */
+static PRUint32
+seconds_since_midnight(PRTime when)
+{
+ PRExplodedTime exp;
+
+ PR_ExplodeTime(when, PR_LocalTimeParameters, &exp);
+ return(exp.tm_hour * 3600 + exp.tm_min * 60 + exp.tm_sec);
+}
+
+
+/*
+ * Return 1 if "now" is within the schedule window
+ * specified by "si", 0 otherwise.
+ */
+static int
+time_in_window(PRTime now, schedule_item *si)
+{
+ unsigned char dow = 1 << day_of_week(now);
+ int return_value = 0;
+
+ if (dow & si->dow)
+ {
+ PRUint32 nowsec = seconds_since_midnight(now);
+
+ return_value = (nowsec >= si->start) && (nowsec <= si->end);
+ }
+
+ return return_value;
+}
+
+
+
+/*
+ * Returns a non-zero value if the current time is within a
+ * replication window and if scheduling constraints are all met.
+ * Otherwise, returns zero.
+ */
+
+int
+schedule_in_window_now (Schedule *sch)
+{
+ int rc;
+
+ PR_ASSERT(NULL != sch);
+ PR_Lock(sch->lock);
+
+ rc = schedule_in_window_now_nolock(sch);
+
+ PR_Unlock(sch->lock);
+
+ return rc;
+}
+
+/* Must be called under sch->lock */
+static int
+schedule_in_window_now_nolock(Schedule *sch)
+{
+ int return_value = 0;
+
+ if (NULL == sch->schedule_list)
+ {
+ /* Absence of a schedule is the same as 0000-2359 0123456 */
+ return_value = 1;
+ }
+ else
+ {
+ schedule_item *si = sch->schedule_list;
+ PRTime now;
+ now = PR_Now();
+ while (NULL != si)
+ {
+ if (time_in_window(now, si))
+ {
+ /* XXX check backoff timers??? */
+ return_value = 1;
+ break;
+ }
+ si = si->next;
+ }
+ }
+
+ return return_value;
+}
+
+
+
+/*
+ * Calculate the next time (expressed as a PRTime) when this
+ * schedule item will change state (from open to close or vice versa).
+ */
+static PRTime
+next_change_time(schedule_item *si, PRTime now, PRBool start)
+{
+ PRUint32 nowsec = seconds_since_midnight(now);
+ PRUint32 sec_til_change;
+ PRUint32 change_time;
+ PRExplodedTime exp;
+ PRInt32 dow = day_of_week(now);
+ unsigned char dow_bit = 1 << dow;
+ unsigned char next_dow;
+
+ if (start) /* we are looking for the next window opening */
+ {
+ change_time = si->start;
+ }
+ else /* we are looking for the next window closing */
+ {
+ /* open range is inclusive - so we need to add a minute if we are looking for close time */
+ change_time = si->end + SECONDS_PER_MINUTE;
+ }
+
+ /* we are replicating today and next change is also today */
+ if ((dow_bit & si->dow) && (nowsec < change_time))
+ {
+ sec_til_change = change_time - nowsec;
+ }
+ else /* not replicating today or the change already occured today */
+ {
+ int i;
+
+ /* find next day when we replicate */
+ for (i = 1; i <= DAYS_PER_WEEK; i++)
+ {
+ next_dow = 1 << ((dow + i) % DAYS_PER_WEEK);
+ if (next_dow & si->dow)
+ break;
+ }
+
+ sec_til_change = change_time + i * SECONDS_PER_DAY - nowsec;
+ }
+
+ PR_ExplodeTime(now, PR_LocalTimeParameters, &exp);
+ exp.tm_sec += sec_til_change;
+
+
+ PR_NormalizeTime(&exp, PR_LocalTimeParameters);
+ return PR_ImplodeTime(&exp);
+}
+
+
+
+/*
+ * Returns the next time that replication is scheduled to occur.
+ * Returns 0 if there is no time in the future that replication
+ * will begin (e.g. there's no schedule at all).
+ */
+PRTime
+schedule_next(Schedule *sch)
+{
+ PRTime tm;
+
+ PR_ASSERT(NULL != sch);
+ PR_Lock(sch->lock);
+
+ tm = schedule_next_nolock (sch, PR_TRUE);
+
+ PR_Unlock(sch->lock);
+
+ return tm;
+}
+
+/* Must be called under sch->lock */
+static PRTime
+schedule_next_nolock (Schedule *sch, PRBool start)
+{
+
+ PRTime closest_time = LL_Zero();
+
+ if (NULL != sch->schedule_list)
+ {
+ schedule_item *si = sch->schedule_list;
+ PRTime now = PR_Now();
+ unsigned char dow = 1 << day_of_week(now);
+
+ while (NULL != si)
+ {
+ PRTime tmp_time;
+
+ /* Check if this item's change time is sooner than the others */
+ tmp_time = next_change_time(si, now, start);
+ if (LL_IS_ZERO(closest_time))
+ {
+ LL_ADD(closest_time, tmp_time, LL_Zero()); /* Really just an asignment */
+ }
+ else if (LL_CMP(tmp_time, <, closest_time))
+ {
+ LL_ADD(closest_time, tmp_time, LL_Zero()); /* Really just an asignment */
+ }
+
+ si = si->next;
+ }
+ }
+
+ return closest_time;
+}
+
+
+
+
+/*
+ * Called by the enclosing object (replsupplier) when a change within the
+ * replicated area has occurred. This allows the scheduler to update its
+ * internal counters, timers, etc. Returns a non-zero value if replication
+ * should commence, zero if it should not.
+ */
+int
+schedule_notify(Schedule *sch, Slapi_PBlock *pb)
+{
+ int return_value = 0;
+
+ return return_value;
+}
+
+
+
+
+/*
+ * Provide a list of attributes which, if changed,
+ * will cause replication to commence as soon as possible. There
+ * is also a flag that tells the scheduler if the update of a
+ * priority attribute should cause the schedule to be overridden,
+ * e.g. if the administrator wants password changes to propagate
+ * even if not in a replication window.
+ *
+ * This function consumes "prio_attrs" and assumes management
+ * of the memory.
+ */
+void
+schedule_set_priority_attributes(Schedule *sch, char **prio_attrs, int override_schedule)
+{
+ PR_ASSERT(NULL != sch);
+ PR_Lock(sch->lock);
+ if (NULL != sch->prio_attrs)
+ {
+ int i;
+ for (i = 0; NULL != prio_attrs[i]; i++) {
+ slapi_ch_free((void **)&sch->prio_attrs[i]);
+ }
+ slapi_ch_free((void **)&sch->prio_attrs);
+ }
+ sch->prio_attrs = prio_attrs;
+ sch->prio_attrs_override_schedule = override_schedule;
+
+ PR_Unlock(sch->lock);
+}
+
+
+
+
+
+/*
+ * Set the time, in seconds, that replication will wait after a change is
+ * available before propagating it. This capability will allow multiple
+ * updates to be coalesced into a single replication session.
+ */
+void
+schedule_set_startup_delay(Schedule *sch, size_t startup_delay)
+{
+ PR_ASSERT(NULL != sch);
+ PR_Lock(sch->lock);
+ sch->startup_delay = startup_delay;
+ PR_Unlock(sch->lock);
+}
+
+
+
+
+
+/*
+ * Set the maximum number of pending changes allowed to accumulate
+ * before a replication session is begun.
+ */
+void
+schedule_set_maximum_backlog(Schedule *sch, size_t max_backlog)
+{
+ PR_ASSERT(NULL != sch);
+ PR_Lock(sch->lock);
+ sch->max_backlog = max_backlog;
+ PR_Unlock(sch->lock);
+}
+
+
+
+
+
+/*
+ * Notify the scheduler that a replication session completed at a certain
+ * time. There is also a status argument that says more about the session's
+ * termination (normal, abnormal), which the scheduler uses in determining
+ * the backoff strategy.
+ */
+void
+schedule_notify_session(Schedule *sch, PRTime session_end_time, unsigned int status)
+{
+ PR_ASSERT(NULL != sch);
+ PR_Lock(sch->lock);
+ sch->last_session_end = session_end_time;
+ sch->last_session_status = status;
+ if (REPLICATION_SESSION_SUCCESS == status)
+ {
+ sch->last_successful_session_end = session_end_time;
+ }
+ PR_Unlock(sch->lock);
+}
+
+/* schedule an event that will fire the next time the update window state
+ changes from open to closed or vice versa */
+static void
+schedule_window_state_change_event (Schedule *sch)
+{
+ time_t wakeup_time;
+ PRTime tm;
+ int window_opened;
+ char *timestr = NULL;
+
+ /* if we have a schedule and a callback function is registerd -
+ register an event with the event queue */
+ if (sch->schedule_list && sch->callback_fn)
+ {
+ /* ONREPL what if the window is really small and by the time we are done
+ with the computation - we cross window boundary.
+ I think we should put some constrains on schedule to avoid that */
+
+ window_opened = schedule_in_window_now_nolock(sch);
+
+ tm = schedule_next_nolock(sch, !window_opened);
+
+ wakeup_time = PRTime2time_t (tm);
+
+ /* schedule the event */
+ sch->pending_event = slapi_eq_once(window_state_changed, sch, wakeup_time);
+
+ timestr = get_timestring(&wakeup_time);
+ slapi_log_error (SLAPI_LOG_REPL, repl_plugin_name, "%s: Update window will %s at %s\n",
+ sch->session_id,
+ window_opened ? "close" : "open", timestr);
+ free_timestring(timestr);
+ timestr = NULL;
+ }
+}
+
+/* this function is called by the even queue the next time
+ the window is opened or closed */
+static void
+window_state_changed (time_t when, void *arg)
+{
+ Schedule *sch = (Schedule*)arg;
+ int open;
+
+ PR_ASSERT (sch);
+
+ PR_Lock(sch->lock);
+
+ open = schedule_in_window_now_nolock(sch);
+
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "%s: Update window is now %s\n",
+ sch->session_id,
+ open ? "open" : "closed");
+
+ /* schedule next event */
+ schedule_window_state_change_event (sch);
+
+ /* notify the agreement */
+ sch->callback_fn (sch->callback_arg, open);
+
+ PR_Unlock(sch->lock);
+}
+
+/* cancel the event registered with the event queue */
+static void
+unschedule_window_state_change_event (Schedule *sch)
+{
+ if (sch->pending_event)
+ {
+ slapi_eq_cancel(sch->pending_event);
+ sch->pending_event = NULL;
+ }
+}
+
+static time_t
+PRTime2time_t (PRTime tm)
+{
+ PRInt64 rt;
+
+ PR_ASSERT (tm);
+
+ LL_DIV(rt, tm, PR_USEC_PER_SEC);
+
+ return (time_t)rt;
+}
+
+/*
+ * Parse a schedule line.
+ * The format is:
+ * <start>-<end> <day_of_week>
+ * <start> and <end> are in 24-hour time
+ * <day_of_week> is like cron(5): 0 = Sunday, 1 = Monday, etc.
+ *
+ * The schedule item "*" is equivalen to 0000-2359 0123456
+ *
+ * Returns a pointer to a schedule item on success, NULL if the
+ * schedule item cannot be parsed.
+ */
+static schedule_item *
+parse_schedule_value(const Slapi_Value *v)
+{
+#define RANGE_VALID(p, limit) \
+ ((p + 9) < limit && \
+ isdigit(p[0]) && \
+ isdigit(p[1]) && \
+ isdigit(p[2]) && \
+ isdigit(p[3]) && \
+ ('-' == p[4]) && \
+ isdigit(p[5]) && \
+ isdigit(p[6]) && \
+ isdigit(p[7]) && \
+ isdigit(p[8]))
+
+ schedule_item *si = NULL;
+ int valid = 0;
+ const struct berval *sch_bval;
+
+ if (NULL != v && (sch_bval = slapi_value_get_berval(v)) != NULL &&
+ NULL != sch_bval && sch_bval->bv_len > 0 && NULL != sch_bval->bv_val )
+ {
+ char *p = sch_bval->bv_val;
+ char *limit = p + sch_bval->bv_len;
+
+ si = (schedule_item *)slapi_ch_malloc(sizeof(schedule_item));
+ si->next = NULL;
+ si->start = 0UL;
+ si->end = SECONDS_PER_DAY;
+ si->dow = ALL_DAYS;
+
+ if (*p == '*')
+ {
+ valid = 1;
+ goto done;
+ }
+ else
+ {
+ if (RANGE_VALID(p, limit))
+ {
+ si->start = ((strntoul(p, 2, 10) * 60) +
+ strntoul(p + 2, 2, 10)) * 60;
+ p += 5;
+ si->end = ((strntoul(p, 2, 10) * 60) +
+ strntoul(p + 2, 2, 10)) * 60;
+ p += 4;
+
+ /* ONREPL - for now wi don't allow items that span multiple days.
+ See note in the beginning of the file for more details. */
+ /* ONREPL - we should also decide on the minimum of the item size */
+ if (si->start > si->end)
+ {
+ valid = 0;
+ goto done;
+ }
+
+ if (p < limit && ' ' == *p)
+ {
+ /* Specific days of week */
+ si->dow = 0;
+ while (++p < limit)
+ {
+ if (!isdigit(*p))
+ {
+ valid = 0;
+ goto done;
+ }
+ si->dow |= (1 << strntoul(p, 1, 10));
+
+ }
+ }
+ valid = 1;
+ }
+ }
+ }
+
+done:
+ if (!valid)
+ {
+ slapi_ch_free((void **)&si);
+ }
+ return si;
+}
diff --git a/ldap/servers/plugins/replication/repl5_tot_protocol.c b/ldap/servers/plugins/replication/repl5_tot_protocol.c
new file mode 100644
index 00000000..45e91f3b
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl5_tot_protocol.c
@@ -0,0 +1,372 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+/* repl5_tot_protocol.c */
+/*
+
+ The tot_protocol object implements the DS 5.0 multi-master total update
+ replication protocol, used to (re)populate a replica.
+
+*/
+
+#include "repl.h"
+#include "repl5.h"
+#include "repl5_prot_private.h"
+
+/* Private data structures */
+typedef struct repl5_tot_private
+{
+ Repl_Protocol *rp;
+ Repl_Agmt *ra;
+ PRLock *lock;
+ PRUint32 eventbits;
+} repl5_tot_private;
+
+typedef struct callback_data
+{
+ Private_Repl_Protocol *prp;
+ int rc;
+ unsigned long num_entries;
+ time_t sleep_on_busy;
+ time_t last_busy;
+} callback_data;
+
+/*
+ * Number of window seconds to wait until we programmatically decide
+ * that the replica has got out of BUSY state
+ */
+#define SLEEP_ON_BUSY_WINDOW (10)
+
+/* Helper functions */
+static void get_result (int rc, void *cb_data);
+static int send_entry (Slapi_Entry *e, void *callback_data);
+static void repl5_tot_delete(Private_Repl_Protocol **prp);
+
+/*
+ * Completely refresh a replica. The basic protocol interaction goes
+ * like this:
+ * - Acquire Replica by sending a StartReplicationRequest extop, with the
+ * total update protocol OID and supplier's ruv.
+ * - Send a series of extended operations containing entries.
+ * - send an EndReplicationRequest extended operation
+ */
+static void
+repl5_tot_run(Private_Repl_Protocol *prp)
+{
+ int rc;
+ callback_data cb_data;
+ Slapi_PBlock *pb;
+ LDAPControl **ctrls;
+ PRBool replica_acquired = PR_FALSE;
+ char *hostname = NULL;
+ int portnum = 0;
+ Slapi_DN *area_sdn = NULL;
+ CSN *remote_schema_csn = NULL;
+
+ PR_ASSERT(NULL != prp);
+
+ prp->stopped = 0;
+ if (prp->terminate)
+ {
+ prp->stopped = 1;
+ goto done;
+ }
+
+ conn_set_timeout(prp->conn, agmt_get_timeout(prp->agmt));
+
+ /* acquire remote replica */
+ agmt_set_last_init_start(prp->agmt, current_time());
+ rc = acquire_replica (prp, REPL_NSDS50_TOTAL_PROTOCOL_OID, NULL /* ruv */);
+ /* We never retry total protocol, even in case a transient error.
+ This is because if somebody already updated the replica we don't
+ want to do it again */
+ if (rc != ACQUIRE_SUCCESS)
+ {
+ int optype, ldaprc;
+ conn_get_error(prp->conn, &optype, &ldaprc);
+ agmt_set_last_init_status(prp->agmt, ldaprc,
+ prp->last_acquire_response_code, NULL);
+ goto done;
+ }
+ else if (prp->terminate)
+ {
+ conn_disconnect(prp->conn);
+ prp->stopped = 1;
+ goto done;
+ }
+
+ hostname = agmt_get_hostname(prp->agmt);
+ portnum = agmt_get_port(prp->agmt);
+
+ agmt_set_last_init_status(prp->agmt, 0, 0, "Total schema update in progress");
+ remote_schema_csn = agmt_get_consumer_schema_csn ( prp->agmt );
+ rc = conn_push_schema(prp->conn, &remote_schema_csn);
+ if (CONN_SCHEMA_UPDATED != rc && CONN_SCHEMA_NO_UPDATE_NEEDED != rc)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Warning: unable to "
+ "replicate schema to host %s, port %d. Continuing with "
+ "total update session.\n",
+ hostname, portnum);
+ /* But keep going */
+ agmt_set_last_init_status(prp->agmt, 0, rc, "Total schema update failed");
+ }
+ else
+ {
+ agmt_set_last_init_status(prp->agmt, 0, 0, "Total schema update succeeded");
+ }
+
+ /* ONREPL - big assumption here is that entries a returned in the id order
+ and that the order implies that perent entry is always ahead of the
+ child entry in the list. Otherwise, the consumer would not be
+ properly updated because bulk import at the moment skips orphand entries. */
+ /* XXXggood above assumption may not be valid if orphaned entry moved???? */
+
+ agmt_set_last_init_status(prp->agmt, 0, 0, "Total update in progress");
+
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Beginning total update of replica "
+ "\"%s\".\n", agmt_get_long_name(prp->agmt));
+ pb = slapi_pblock_new ();
+
+ /* RMREPL - need to send schema here */
+
+ area_sdn = agmt_get_replarea(prp->agmt);
+ /* we need to provide managedsait control so that referral entries can
+ be replicated */
+ ctrls = (LDAPControl **)slapi_ch_calloc (3, sizeof (LDAPControl *));
+ ctrls[0] = create_managedsait_control ();
+ ctrls[1] = create_backend_control(area_sdn);
+
+ slapi_search_internal_set_pb (pb, slapi_sdn_get_dn (area_sdn),
+ LDAP_SCOPE_SUBTREE, "(|(objectclass=ldapsubentry)(objectclass=nstombstone)(nsuniqueid=*))", NULL, 0, ctrls, NULL,
+ repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0);
+ cb_data.prp = prp;
+ cb_data.rc = 0;
+ cb_data.num_entries = 0UL;
+ cb_data.sleep_on_busy = 0UL;
+ cb_data.last_busy = current_time ();
+
+ /* this search get all the entries from the replicated area including tombstones
+ and referrals */
+ slapi_search_internal_callback_pb (pb, &cb_data /* callback data */,
+ get_result /* result callback */,
+ send_entry /* entry callback */,
+ NULL /* referral callback*/);
+ slapi_pblock_destroy (pb);
+ agmt_set_last_init_end(prp->agmt, current_time());
+ rc = cb_data.rc;
+ release_replica(prp);
+ slapi_sdn_free(&area_sdn);
+
+ if (rc != LDAP_SUCCESS)
+ {
+ slapi_log_error (SLAPI_LOG_REPL, repl_plugin_name, "%s: repl5_tot_run: "
+ "failed to obtain data to send to the consumer; LDAP error - %d\n",
+ agmt_get_long_name(prp->agmt), rc);
+ agmt_set_last_init_status(prp->agmt, rc, 0, "Total update aborted");
+ } else {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Finished total update of replica "
+ "\"%s\". Sent %d entries.\n", agmt_get_long_name(prp->agmt), cb_data.num_entries);
+ agmt_set_last_init_status(prp->agmt, 0, 0, "Total update succeeded");
+ }
+
+done:
+ slapi_ch_free_string(&hostname);
+ prp->stopped = 1;
+}
+
+static int
+repl5_tot_stop(Private_Repl_Protocol *prp)
+{
+ int return_value;
+ int seconds = 600;
+ PRIntervalTime start, maxwait, now;
+
+ prp->terminate = 1;
+ maxwait = PR_SecondsToInterval(seconds);
+ start = PR_IntervalNow();
+ now = start;
+ while (!prp->stopped && ((now - start) < maxwait))
+ {
+ DS_Sleep(PR_SecondsToInterval(1));
+ now = PR_IntervalNow();
+ }
+ if (!prp->stopped)
+ {
+ /* Isn't listening. Disconnect from the replica. */
+ slapi_log_error (SLAPI_LOG_REPL, repl_plugin_name, "repl5_tot_run: "
+ "protocol not stopped after waiting for %d seconds "
+ "for agreement %s\n", PR_IntervalToSeconds(now-start),
+ agmt_get_long_name(prp->agmt));
+ conn_disconnect(prp->conn);
+ return_value = -1;
+ }
+ else
+ {
+ return_value = 0;
+ }
+
+ return return_value;
+}
+
+
+
+static int
+repl5_tot_status(Private_Repl_Protocol *prp)
+{
+ int return_value = 0;
+ return return_value;
+}
+
+
+
+static void
+repl5_tot_noop(Private_Repl_Protocol *prp)
+{
+ /* noop */
+}
+
+
+Private_Repl_Protocol *
+Repl_5_Tot_Protocol_new(Repl_Protocol *rp)
+{
+ repl5_tot_private *rip = NULL;
+ Private_Repl_Protocol *prp = (Private_Repl_Protocol *)slapi_ch_malloc(sizeof(Private_Repl_Protocol));
+ prp->delete = repl5_tot_delete;
+ prp->run = repl5_tot_run;
+ prp->stop = repl5_tot_stop;
+ prp->status = repl5_tot_status;
+ prp->notify_update = repl5_tot_noop;
+ prp->notify_agmt_changed = repl5_tot_noop;
+ prp->notify_window_opened = repl5_tot_noop;
+ prp->notify_window_closed = repl5_tot_noop;
+ prp->update_now = repl5_tot_noop;
+ if ((prp->lock = PR_NewLock()) == NULL)
+ {
+ goto loser;
+ }
+ if ((prp->cvar = PR_NewCondVar(prp->lock)) == NULL)
+ {
+ goto loser;
+ }
+ prp->stopped = 1;
+ prp->terminate = 0;
+ prp->eventbits = 0;
+ prp->conn = prot_get_connection(rp);
+ prp->agmt = prot_get_agreement(rp);
+ rip = (void *)slapi_ch_malloc(sizeof(repl5_tot_private));
+ rip->rp = rp;
+ prp->private = (void *)rip;
+ prp->replica_acquired = PR_FALSE;
+ return prp;
+loser:
+ repl5_tot_delete(&prp);
+ return NULL;
+}
+
+static void
+repl5_tot_delete(Private_Repl_Protocol **prp)
+{
+}
+
+static
+void get_result (int rc, void *cb_data)
+{
+ PR_ASSERT (cb_data);
+ ((callback_data*)cb_data)->rc = rc;
+}
+
+static
+int send_entry (Slapi_Entry *e, void *cb_data)
+{
+ int rc;
+ Private_Repl_Protocol *prp;
+ BerElement *bere;
+ struct berval *bv;
+ unsigned long *num_entriesp;
+ time_t *sleep_on_busyp;
+ time_t *last_busyp;
+
+ PR_ASSERT (cb_data);
+
+ prp = ((callback_data*)cb_data)->prp;
+ num_entriesp = &((callback_data *)cb_data)->num_entries;
+ sleep_on_busyp = &((callback_data *)cb_data)->sleep_on_busy;
+ last_busyp = &((callback_data *)cb_data)->last_busy;
+ PR_ASSERT (prp);
+
+ if (prp->terminate)
+ {
+ conn_disconnect(prp->conn);
+ prp->stopped = 1;
+ ((callback_data*)cb_data)->rc = -1;
+ return -1;
+ }
+
+ /* skip ruv tombstone - need to do this because it might be
+ more up to date then the data we are sending to the client.
+ RUV is sent separately via the protocol */
+ if (is_ruv_tombstone_entry (e))
+ return 0;
+
+ /* ONREPL we would purge copiedFrom and copyingFrom here but I decided against it.
+ Instead, it will get removed when this replica stops being 4.0 consumer and
+ then propagated to all its consumer */
+
+ /* convert the entry to the on the wire format */
+ bere = entry2bere(e);
+ if (bere == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "%s: send_entry: Encoding Error\n",
+ agmt_get_long_name(prp->agmt));
+ ((callback_data*)cb_data)->rc = -1;
+ return -1;
+ }
+
+ rc = ber_flatten(bere, &bv);
+ ber_free (bere, 1);
+ if (rc != 0)
+ {
+ ((callback_data*)cb_data)->rc = -1;
+ return -1;
+ }
+
+ do {
+ /* push the entry to the consumer */
+ rc = conn_send_extended_operation(prp->conn, REPL_NSDS50_REPLICATION_ENTRY_REQUEST_OID,
+ bv /* payload */, NULL /* retoidp */,
+ NULL /* retdatap */, NULL /* update_control */,
+ NULL /* returned_controls */);
+
+ if (rc == CONN_BUSY) {
+ time_t now = current_time ();
+ if ((now - *last_busyp) < (*sleep_on_busyp + 10)) {
+ *sleep_on_busyp +=5;
+ }
+ else {
+ *sleep_on_busyp = 5;
+ }
+ *last_busyp = now;
+
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "Replica \"%s\" is busy. Waiting %ds while"
+ " it finishes processing its current import queue\n",
+ agmt_get_long_name(prp->agmt), *sleep_on_busyp);
+ DS_Sleep(PR_SecondsToInterval(*sleep_on_busyp));
+ }
+ }
+ while (rc == CONN_BUSY);
+
+ ber_bvfree(bv);
+ (*num_entriesp)++;
+
+ if (CONN_OPERATION_SUCCESS == rc) {
+ return 0;
+ } else {
+ ((callback_data*)cb_data)->rc = rc;
+ return -1;
+ }
+}
+
diff --git a/ldap/servers/plugins/replication/repl5_total.c b/ldap/servers/plugins/replication/repl5_total.c
new file mode 100644
index 00000000..66dcc353
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl5_total.c
@@ -0,0 +1,869 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+
+/*
+ repl5_total.c - code that implements a total replica update.
+
+ The requestValue of the NSDS50ReplicationEntry looks like this:
+
+ requestValue ::= SEQUENCE {
+ uniqueid OCTET STRING,
+ dn LDAPDN,
+ annotatedAttributes AnnotatedAttributeList
+ }
+
+ AnnotatedAttributeList ::= SET OF SEQUENCE {
+ attributeType AttributeDescription,
+ attributeDeletionCSN OCTET STRING OPTIONAL,
+ attributeDeleted BOOLEAN DEFAULT FALSE,
+ annotatedValues SET OF AnnotatedValue
+ }
+
+ AnnotatedValue ::= SEQUENCE {
+ value AttributeValue,
+ valueDeleted BOOLEAN DEFAULT FALSE,
+ valueCSNSet SEQUENCE OF ValueCSN,
+ }
+
+ ValueCSN ::= SEQUENCE {
+ CSNType ENUMERATED {
+ valuePresenceCSN (1),
+ valueDeletionCSN (2),
+ valueDistinguishedCSN (3)
+ }
+ CSN OCTET STRING,
+ }
+*/
+
+#include "repl5.h"
+
+#define CSN_TYPE_VALUE_UPDATED_ON_WIRE 1
+#define CSN_TYPE_VALUE_DELETED_ON_WIRE 2
+#define CSN_TYPE_VALUE_DISTINGUISHED_ON_WIRE 3
+
+/* #define GORDONS_PATENTED_BER_DEBUG 1 */
+#ifdef GORDONS_PATENTED_BER_DEBUG
+#define BER_DEBUG(a) printf(a)
+#else
+#define BER_DEBUG(a)
+#endif
+
+/* Forward declarations */
+static int my_ber_printf_csn(BerElement *ber, const CSN *csn, const CSNType t);
+static int my_ber_printf_value(BerElement *ber, const char *type,
+ const Slapi_Value *value, PRBool deleted);
+static int my_ber_printf_attr (BerElement *ber, Slapi_Attr *attr, PRBool deleted);
+static int my_ber_scanf_attr (BerElement *ber, Slapi_Attr **attr, PRBool *deleted);
+static int my_ber_scanf_value(BerElement *ber, Slapi_Value **value, PRBool *deleted);
+
+/*
+ * Get a Slapi_Entry ready to send over the wire as part of
+ * a total update protocol stream. Convert the entry and all
+ * of its state information to a BerElement which will be the
+ * payload of an extended LDAP operation.
+ *
+ * Entries consist of:
+ * - An entry DN
+ * - A uniqueID
+ * - A set of present attributes, each of which consists of:
+ * - A set of present values, each of which consists of:
+ * - A value
+ * - A set of CSNs
+ * - A set of deleted values, each of which consists of:
+ * - A value
+ * - A set of CSNs
+ * - A set of deleted attibutes, each of which consists of:
+ * - An attribute type
+ * - A set of CSNs. Note that this list of CSNs will always contain exactly one CSN.
+ *
+ * This all gets mashed into one BerElement, ready to be blasted over the wire to
+ * a replica.
+ *
+ */
+BerElement *
+entry2bere(const Slapi_Entry *e)
+{
+ BerElement *ber = NULL;
+ const char *str = NULL;
+ const char *dnstr = NULL;
+ char *type;
+ Slapi_DN *sdn = NULL;
+ Slapi_Attr *attr = NULL, *prev_attr;
+ int rc;
+
+ PR_ASSERT(NULL != e);
+
+ if ((ber = ber_alloc()) == NULL)
+ {
+ goto loser;
+ }
+ BER_DEBUG("{");
+ if (ber_printf(ber, "{") == -1) /* Begin outer sequence */
+ {
+ goto loser;
+ }
+
+ /* Get the entry's uniqueid */
+ if ((str = slapi_entry_get_uniqueid(e)) == NULL)
+ {
+ goto loser;
+ }
+ BER_DEBUG("s(uniqueid)");
+ if (ber_printf(ber, "s", str) == -1)
+ {
+ goto loser;
+ }
+
+ /* Get the entry's DN */
+ if ((sdn = slapi_entry_get_sdn((Slapi_Entry *)e)) == NULL) /* XXXggood had to cast away const */
+ {
+ goto loser;
+ }
+ if ((dnstr = slapi_sdn_get_dn(sdn)) == NULL)
+ {
+ goto loser;
+ }
+ BER_DEBUG("s(dn)");
+ if (ber_printf(ber, "s", dnstr) == -1)
+ {
+ goto loser;
+ }
+
+ /* Next comes the annoted list of the entry's attributes */
+ BER_DEBUG("[");
+ if (ber_printf(ber, "[") == -1) /* Begin set of attributes */
+ {
+ goto loser;
+ }
+ /*
+ * We iterate over all of the non-deleted attributes first.
+ */
+ slapi_entry_first_attr(e, &attr);
+ while (NULL != attr)
+ {
+ /* ONREPL - skip uniqueid attribute since we already sent uniqueid
+ This is a hack; need to figure a better way of storing uniqueid
+ in an entry */
+ slapi_attr_get_type (attr, &type);
+ if (strcasecmp (type, SLAPI_ATTR_UNIQUEID) != 0)
+ {
+ /* Process this attribute */
+ rc = my_ber_printf_attr (ber, attr, PR_FALSE);
+ if (rc != 0)
+ {
+ goto loser;
+ }
+ }
+
+ prev_attr = attr;
+ slapi_entry_next_attr(e, prev_attr, &attr);
+ }
+
+ /*
+ * Now iterate over the deleted attributes.
+ */
+ entry_first_deleted_attribute(e, &attr);
+ while (attr != NULL)
+ {
+ /* Process this attribute */
+ rc = my_ber_printf_attr (ber, attr, PR_TRUE);
+ if (rc != 0)
+ {
+ goto loser;
+ }
+ entry_next_deleted_attribute(e, &attr);
+ }
+ BER_DEBUG("]");
+ if (ber_printf(ber, "]") == -1) /* End set for attributes */
+ {
+ goto loser;
+ }
+ BER_DEBUG("}");
+ if (ber_printf(ber, "}") == -1) /* End sequence for this entry */
+ {
+ goto loser;
+ }
+
+ /* If we get here, everything went ok */
+ BER_DEBUG("\n");
+ goto free_and_return;
+loser:
+ if (NULL != ber)
+ {
+ ber_free(ber, 1);
+ ber = NULL;
+ }
+
+free_and_return:
+ return ber;
+}
+
+
+/*
+ * Helper function - convert a CSN to a string and ber_printf() it.
+ */
+static int
+my_ber_printf_csn(BerElement *ber, const CSN *csn, const CSNType t)
+{
+ char csn_str[CSN_STRSIZE];
+ unsigned long len;
+ int rc = -1;
+ int csn_type_as_ber = -1;
+
+ switch (t)
+ {
+ case CSN_TYPE_VALUE_UPDATED:
+ csn_type_as_ber = CSN_TYPE_VALUE_UPDATED_ON_WIRE;
+ break;
+ case CSN_TYPE_VALUE_DELETED:
+ csn_type_as_ber = CSN_TYPE_VALUE_DELETED_ON_WIRE;
+ break;
+ case CSN_TYPE_VALUE_DISTINGUISHED:
+ csn_type_as_ber = CSN_TYPE_VALUE_DISTINGUISHED_ON_WIRE;
+ break;
+ case CSN_TYPE_ATTRIBUTE_DELETED:
+ break;
+ default:
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "my_ber_printf_csn: unknown "
+ "csn type %d encountered.\n", (int)t);
+ return -1;
+ }
+
+ csn_as_string(csn,PR_FALSE,csn_str);
+
+ /* we don't send type for attr csn since there is only one */
+ if (t == CSN_TYPE_ATTRIBUTE_DELETED)
+ {
+ rc = ber_printf(ber, "s", csn_str);
+ BER_DEBUG("s(csn_str)");
+ }
+ else
+ {
+ len = CSN_STRSIZE;
+ rc = ber_printf(ber, "{es}", csn_type_as_ber, csn_str);
+ BER_DEBUG("{e(csn type)s(csn)}");
+ }
+
+ return rc;
+}
+
+
+/*
+ * Send a single annotated attribute value.
+ */
+static int
+my_ber_printf_value(BerElement *ber, const char *type, const Slapi_Value *value, PRBool deleted)
+{
+ const struct berval *bval = NULL;
+ int rc = -1;
+ const CSNSet *csnset;
+ void *cookie;
+ CSN *csn;
+ CSNType t;
+
+ bval = slapi_value_get_berval(value);
+ BER_DEBUG("{o(value)");
+ if (ber_printf(ber, "{o", bval->bv_val, bval->bv_len) == -1) /* Start sequence */
+ {
+ goto done;
+ }
+
+/* if (ber_printf(ber, "o", bval->bv_val, bval->bv_len) == -1)
+ {
+ goto done;
+ } */
+
+ if (deleted)
+ {
+ BER_DEBUG("b(deleted flag)");
+ if (ber_printf (ber, "b", PR_TRUE) == -1)
+ {
+ goto done;
+ }
+ }
+ /* Send value CSN list */
+ BER_DEBUG("{");
+ if (ber_printf(ber, "{") == -1) /* Start set */
+ {
+ goto done;
+ }
+
+ /* Iterate over the sequence of CSNs. */
+ csnset = value_get_csnset (value);
+ if (csnset)
+ {
+ for (cookie = csnset_get_first_csn (csnset, &csn, &t); NULL != cookie;
+ cookie = csnset_get_next_csn (csnset, cookie, &csn, &t))
+ {
+ /* Don't send any adcsns, since that was already sent */
+ if (t != CSN_TYPE_ATTRIBUTE_DELETED)
+ {
+ if (my_ber_printf_csn(ber, csn, t) == -1)
+ {
+ goto done;
+ }
+ }
+ }
+ }
+
+ BER_DEBUG("}");
+ if (ber_printf(ber, "}") == -1) /* End CSN sequence */
+ {
+ goto done;
+ }
+ BER_DEBUG("}");
+ if (ber_printf(ber, "}") == -1) /* End sequence */
+ {
+ goto done;
+ }
+
+ /* Everything's ok */
+ rc = 0;
+
+done:
+ return rc;
+
+}
+
+/* send a single attribute */
+static int
+my_ber_printf_attr (BerElement *ber, Slapi_Attr *attr, PRBool deleted)
+{
+ Slapi_Value *value;
+ char *type;
+ int i;
+ const CSN *csn;
+
+ /* First, send the type */
+ slapi_attr_get_type(attr, &type);
+ BER_DEBUG("{s(type ");
+ BER_DEBUG(type);
+ BER_DEBUG(")");
+ if (ber_printf(ber, "{s", type) == -1) /* Begin sequence for this type */
+ {
+ goto loser;
+ }
+
+ /* Send the attribute deletion CSN if present */
+ csn = attr_get_deletion_csn(attr);
+ if (csn)
+ {
+ if (my_ber_printf_csn(ber, csn, CSN_TYPE_ATTRIBUTE_DELETED) == -1)
+ {
+ goto loser;
+ }
+ }
+
+ /* only send "is deleted" flag for deleted attributes since it defaults to false */
+ if (deleted)
+ {
+ BER_DEBUG("b(del flag)");
+ if (ber_printf (ber, "b", PR_TRUE) == -1)
+ {
+ goto loser;
+ }
+ }
+
+ /*
+ * Iterate through all the values.
+ */
+ BER_DEBUG("[");
+ if (ber_printf(ber, "[") == -1) /* Begin set */
+ {
+ goto loser;
+ }
+
+ /*
+ * Process the non-deleted values first.
+ */
+ i = slapi_attr_first_value(attr, &value);
+ while (i != -1)
+ {
+ if (my_ber_printf_value(ber, type, value, PR_FALSE) == -1)
+ {
+ goto loser;
+ }
+ i= slapi_attr_next_value(attr, i, &value);
+ }
+
+ /*
+ * Now iterate over all of the deleted values.
+ */
+ i= attr_first_deleted_value(attr, &value);
+ while (i != -1)
+ {
+ if (my_ber_printf_value(ber, type, value, PR_TRUE) == -1)
+ {
+ goto loser;
+ }
+ i= attr_next_deleted_value(attr, i, &value);
+ }
+ BER_DEBUG("]");
+ if (ber_printf(ber, "]") == -1) /* End set */
+ {
+ goto loser;
+ }
+
+ BER_DEBUG("}");
+ if (ber_printf(ber, "}") == -1) /* End sequence for this type */
+ {
+ goto loser;
+ }
+
+ return 0;
+loser:
+ return -1;
+}
+
+/*
+ * Get an annotated value from the BerElement. Returns 0 on
+ * success, -1 on failure.
+ */
+static int
+my_ber_scanf_value(BerElement *ber, Slapi_Value **value, PRBool *deleted)
+{
+ struct berval *attrval = NULL;
+ unsigned long len;
+ unsigned long tag;
+ CSN *csn = NULL;
+ char csnstring[CSN_STRSIZE + 1];
+ CSNType csntype;
+ char *lasti;
+
+ PR_ASSERT(ber && value && deleted);
+
+ *value = NULL;
+
+ if (NULL == ber && NULL == value)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "my_ber_scanf_value BAD 1\n");
+ goto loser;
+ }
+
+ /* Each value is a sequence */
+ if (ber_scanf(ber, "{O", &attrval) == -1)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "my_ber_scanf_value BAD 2\n");
+ goto loser;
+ }
+ /* Allocate and fill in the attribute value */
+ if ((*value = slapi_value_new_berval(attrval)) == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "my_ber_scanf_value BAD 3\n");
+ goto loser;
+ }
+
+ /* check if this is a deleted value */
+ if (ber_peek_tag(ber, &len) == LBER_BOOLEAN)
+ {
+ if (ber_scanf(ber, "b", deleted) == -1)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "my_ber_scanf_value BAD 4\n");
+ goto loser;
+ }
+ }
+
+ else /* default is present value */
+ {
+ *deleted = PR_FALSE;
+ }
+
+ /* Read the sequence of CSNs */
+ for (tag = ber_first_element(ber, &len, &lasti);
+ tag != LBER_ERROR && tag != LBER_END_OF_SEQORSET;
+ tag = ber_next_element(ber, &len, lasti))
+ {
+ long csntype_tmp;
+ /* Each CSN is in a sequence that includes a csntype and CSN */
+ len = CSN_STRSIZE;
+ if (ber_scanf(ber, "{es}", &csntype_tmp, csnstring, &len) == -1)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "my_ber_scanf_value BAD 7 - bval is %s\n", attrval->bv_val);
+ goto loser;
+ }
+ switch (csntype_tmp)
+ {
+ case CSN_TYPE_VALUE_UPDATED_ON_WIRE:
+ csntype = CSN_TYPE_VALUE_UPDATED;
+ break;
+ case CSN_TYPE_VALUE_DELETED_ON_WIRE:
+ csntype = CSN_TYPE_VALUE_DELETED;
+ break;
+ case CSN_TYPE_VALUE_DISTINGUISHED_ON_WIRE:
+ csntype = CSN_TYPE_VALUE_DISTINGUISHED;
+ break;
+ default:
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Error: preposterous CSN type "
+ "%d received during total update.\n", csntype_tmp);
+ goto loser;
+ }
+ csn = csn_new_by_string(csnstring);
+ if (csn == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "my_ber_scanf_value BAD 8\n");
+ goto loser;
+ }
+ value_add_csn(*value, csntype, csn);
+ csn_free (&csn);
+ }
+
+ if (ber_scanf(ber, "}") == -1) /* End of annotated attribute value seq */
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "my_ber_scanf_value BAD 10\n");
+ goto loser;
+ }
+
+ if (attrval)
+ ber_bvfree(attrval);
+ return 0;
+
+loser:
+ /* Free any stuff we allocated */
+ if (csn)
+ csn_free (&csn);
+ if (attrval)
+ ber_bvfree(attrval);
+ if (value)
+ {
+ slapi_value_free (value);
+ }
+
+ return -1;
+}
+
+static int
+my_ber_scanf_attr (BerElement *ber, Slapi_Attr **attr, PRBool *deleted)
+{
+ char *attrtype = NULL;
+ CSN *attr_deletion_csn = NULL;
+ PRBool val_deleted;
+ char *lasti;
+ unsigned long len;
+ unsigned long tag;
+ char *str;
+ int rc;
+ Slapi_Value *value;
+
+ PR_ASSERT (ber && attr && deleted);
+
+ /* allocate the attribute */
+ *attr = slapi_attr_new ();
+ if (attr == NULL)
+ {
+ goto loser;
+ }
+
+ if (ber_scanf(ber, "{a", &attrtype) == -1) /* Begin sequence for this attr */
+ {
+ goto loser;
+ }
+
+
+ slapi_attr_init(*attr, attrtype);
+ slapi_ch_free ((void **)&attrtype);
+
+ /* The attribute deletion CSN is next and is optional? */
+ if (ber_peek_tag(ber, &len) == LBER_OCTETSTRING)
+ {
+ if (ber_scanf(ber, "a", &str) == -1)
+ {
+ goto loser;
+ }
+ attr_deletion_csn = csn_new_by_string(str);
+ slapi_ch_free((void **)&str);
+ }
+
+ if (attr_deletion_csn)
+ {
+ rc = attr_set_deletion_csn(*attr, attr_deletion_csn);
+ csn_free (&attr_deletion_csn);
+ if (rc != 0)
+ {
+ goto loser;
+ }
+ }
+
+ /* The "attribute deleted" flag is next, and is optional */
+ if (ber_peek_tag(ber, &len) == LBER_BOOLEAN)
+ {
+ if (ber_scanf(ber, "b", deleted) == -1)
+ {
+ goto loser;
+ }
+ }
+ else /* default is present */
+ {
+ *deleted = PR_FALSE;
+ }
+
+ /* loop over the list of attribute values */
+ for (tag = ber_first_element(ber, &len, &lasti);
+ tag != LBER_ERROR && tag != LBER_END_OF_SEQORSET;
+ tag = ber_next_element(ber, &len, lasti))
+ {
+
+ value = NULL;
+ if (my_ber_scanf_value(ber, &value, &val_deleted) == -1)
+ {
+ goto loser;
+ }
+
+ if (val_deleted)
+ {
+ /* Add the value to the attribute */
+ if (attr_add_deleted_value(*attr, value) == -1) /* attr has ownership of value */
+ {
+ goto loser;
+ }
+ }
+ else
+ {
+ /* Add the value to the attribute */
+ if (slapi_attr_add_value(*attr, value) == -1) /* attr has ownership of value */
+ {
+ goto loser;
+ }
+ }
+ if (value)
+ slapi_value_free(&value);
+ }
+
+ if (ber_scanf(ber, "}") == -1) /* End sequence for this attribute */
+ {
+ goto loser;
+ }
+
+ return 0;
+loser:
+ if (*attr)
+ slapi_attr_free (attr);
+ if (value)
+ slapi_value_free (&value);
+
+ return -1;
+}
+
+/*
+ * Extract the payload from a total update extended operation,
+ * decode it, and produce a Slapi_Entry structure representing a new
+ * entry to be added to the local database.
+ */
+static int
+decode_total_update_extop(Slapi_PBlock *pb, Slapi_Entry **ep)
+{
+ BerElement *tmp_bere = NULL;
+ Slapi_Entry *e = NULL;
+ Slapi_Attr *attr = NULL;
+ char *str = NULL;
+ CSN *dn_csn = NULL;
+ struct berval *extop_value = NULL;
+ char *extop_oid = NULL;
+ unsigned long len;
+ char *lasto;
+ unsigned long tag;
+ int rc;
+ PRBool deleted;
+
+ PR_ASSERT(NULL != pb);
+ PR_ASSERT(NULL != ep);
+
+ slapi_pblock_get(pb, SLAPI_EXT_OP_REQ_OID, &extop_oid);
+ slapi_pblock_get(pb, SLAPI_EXT_OP_REQ_VALUE, &extop_value);
+
+ if (NULL == extop_oid ||
+ strcmp(extop_oid, REPL_NSDS50_REPLICATION_ENTRY_REQUEST_OID) != 0 ||
+ NULL == extop_value)
+ {
+ /* Bogus */
+ goto loser;
+ }
+
+ if ((tmp_bere = ber_init(extop_value)) == NULL)
+ {
+ goto loser;
+ }
+
+ if ((e = slapi_entry_alloc()) == NULL)
+ {
+ goto loser;
+ }
+
+ if (ber_scanf(tmp_bere, "{") == -1) /* Begin outer sequence */
+ {
+ goto loser;
+ }
+
+ /* The entry's uniqueid is first */
+ if (ber_scanf(tmp_bere, "a", &str) == -1)
+ {
+ goto loser;
+ }
+ slapi_entry_set_uniqueid(e, str);
+ str = NULL; /* Slapi_Entry now owns the uniqueid */
+
+ /* The entry's DN is next */
+ if (ber_scanf(tmp_bere, "a", &str) == -1)
+ {
+ goto loser;
+ }
+ slapi_entry_set_dn(e, str);
+ str = NULL; /* Slapi_Entry now owns the dn */
+
+ /* Get the attributes */
+ for ( tag = ber_first_element( tmp_bere, &len, &lasto );
+ tag != LBER_ERROR && tag != LBER_END_OF_SEQORSET;
+ tag = ber_next_element( tmp_bere, &len, lasto ) )
+ {
+
+ if (my_ber_scanf_attr (tmp_bere, &attr, &deleted) != 0)
+ {
+ goto loser;
+ }
+
+ /* Add the attribute to the entry */
+ if (deleted)
+ entry_add_deleted_attribute_wsi(e, attr); /* entry now owns attr */
+ else
+ entry_add_present_attribute_wsi(e, attr); /* entry now owns attr */
+ attr = NULL;
+ }
+
+ if (ber_scanf(tmp_bere, "}") == -1) /* End sequence for this entry */
+ {
+ goto loser;
+ }
+
+ /* Check for ldapsubentries and tombstone entries to set flags properly */
+ slapi_entry_attr_find(e, "objectclass", &attr);
+ if (attr != NULL) {
+ struct berval bv;
+ bv.bv_val = "ldapsubentry";
+ bv.bv_len = strlen(bv.bv_val);
+ if (slapi_attr_value_find(attr, &bv) == 0) {
+ slapi_entry_set_flag(e, SLAPI_ENTRY_LDAPSUBENTRY);
+ }
+ bv.bv_val = SLAPI_ATTR_VALUE_TOMBSTONE;
+ bv.bv_len = strlen(bv.bv_val);
+ if (slapi_attr_value_find(attr, &bv) == 0) {
+ slapi_entry_set_flag(e, SLAPI_ENTRY_FLAG_TOMBSTONE);
+ }
+ }
+
+ /* If we get here, the entry is properly constructed. Return it. */
+
+ rc = 0;
+ *ep = e;
+ goto free_and_return;
+
+loser:
+ rc = -1;
+ /* slapi_ch_free accepts NULL pointer */
+ slapi_ch_free((void **)&str);
+
+ if (NULL != dn_csn)
+ {
+ csn_free(&dn_csn);
+ }
+ if (attr != NULL)
+ {
+ slapi_attr_free (&attr);
+ }
+
+ if (NULL != e)
+ {
+ slapi_entry_free (e);
+ }
+ *ep = NULL;
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Error: could not decode extended "
+ "operation containing entry for total update.\n");
+
+free_and_return:
+ if (NULL != tmp_bere)
+ {
+ ber_free(tmp_bere, 1);
+ tmp_bere = NULL;
+ }
+ return rc;
+}
+
+/*
+ * This plugin entry point is called whenever an NSDS50ReplicationEntry
+ * extended operation is received.
+ */
+int
+multimaster_extop_NSDS50ReplicationEntry(Slapi_PBlock *pb)
+{
+ int rc;
+ Slapi_Entry *e = NULL;
+ Slapi_Connection *conn = NULL;
+ int connid, opid;
+
+ connid = 0;
+ slapi_pblock_get(pb, SLAPI_CONN_ID, &connid);
+ opid = 0;
+ slapi_pblock_get(pb, SLAPI_OPERATION_ID, &opid);
+
+ /* Decode the extended operation */
+ rc = decode_total_update_extop(pb, &e);
+
+ if (0 == rc)
+ {
+#ifdef notdef
+ /*
+ * Just spew LDIF so we're sure we got it right. Later we'll firehose
+ * this into the database import code
+ */
+ int len;
+ char *str = slapi_entry2str_with_options(e, &len,SLAPI_DUMP_UNIQUEID);
+ puts(str);
+ free(str);
+#endif
+
+ rc = slapi_import_entry (pb, e);
+ /* slapi_import_entry return an LDAP error in case of problem
+ * LDAP_BUSY is used to indicate that the import queue is full
+ * and that flow control must happen to stop the supplier
+ * from sending entries
+ */
+ if ((rc != LDAP_SUCCESS) && (rc != LDAP_BUSY))
+ {
+ const char *dn = slapi_entry_get_dn_const(e);
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "Error %d: could not import entry dn %s "
+ "for total update operation conn=%d op=%d\n",
+ rc, dn, connid, opid);
+ rc = -1;
+ }
+
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "Error %d: could not decode the total update extop "
+ "for total update operation conn=%d op=%d\n",
+ rc, connid, opid);
+ }
+
+ if ((rc != 0) && (rc != LDAP_BUSY))
+ {
+ /* just disconnect from the supplier. bulk import is stopped when
+ connection object is destroyed */
+ slapi_pblock_get (pb, SLAPI_CONNECTION, &conn);
+ if (conn)
+ {
+ slapi_disconnect_server(conn);
+ }
+
+ /* cleanup */
+ if (e)
+ {
+ slapi_entry_free (e);
+ }
+ }
+
+ return rc;
+}
diff --git a/ldap/servers/plugins/replication/repl5_updatedn_list.c b/ldap/servers/plugins/replication/repl5_updatedn_list.c
new file mode 100644
index 00000000..02304d19
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl5_updatedn_list.c
@@ -0,0 +1,243 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+/* repl5_updatedn_list.c */
+
+/*
+ This is the internal representation for the list of update DNs in the replica.
+ The list is implemented as a hash table - the key is the normalized DN, and the
+ value is the Slapi_DN representation of the DN
+*/
+
+#include "repl5.h"
+#include "plhash.h"
+
+/* global data */
+
+/* typedef ReplicaUpdateDNList PLHashTable; */
+
+struct repl_enum_data
+{
+ FNEnumDN fn;
+ void *arg;
+};
+
+/* Forward declarations */
+static PRIntn replica_destroy_hash_entry (PLHashEntry *he, PRIntn index, void *arg);
+static PRIntn updatedn_list_enumerate (PLHashEntry *he, PRIntn index, void *hash_data);
+
+static int
+updatedn_compare_dns(const void *d1, const void *d2)
+{
+ return (0 == slapi_sdn_compare((const Slapi_DN *)d1, (const Slapi_DN *)d2));
+}
+
+/* create a new updatedn list - if the entry is given, initialize the list from
+ the replicabinddn values given in the entry */
+ReplicaUpdateDNList
+replica_updatedn_list_new(const Slapi_Entry *entry)
+{
+ /* allocate table */
+ PLHashTable *hash = PL_NewHashTable(4, PL_HashString, PL_CompareStrings,
+ updatedn_compare_dns, NULL, NULL);
+ if (hash == NULL) {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_new_updatedn_list: "
+ "failed to allocate hash table; NSPR error - %d\n",
+ PR_GetError ());
+ return NULL;
+ }
+
+ if (entry) {
+ Slapi_Attr *attr = NULL;
+ if (!slapi_entry_attr_find(entry, attr_replicaBindDn, &attr)) {
+ Slapi_ValueSet *vs = NULL;
+ slapi_attr_get_valueset(attr, &vs);
+ replica_updatedn_list_replace(hash, vs);
+ slapi_valueset_free(vs);
+ }
+ }
+
+ return (ReplicaUpdateDNList)hash;
+}
+
+void
+replica_updatedn_list_free(ReplicaUpdateDNList list)
+{
+ /* destroy the content */
+ PLHashTable *hash = list;
+ PL_HashTableEnumerateEntries(hash, replica_destroy_hash_entry, NULL);
+
+ if (hash)
+ PL_HashTableDestroy(hash);
+}
+
+void
+replica_updatedn_list_replace(ReplicaUpdateDNList list, const Slapi_ValueSet *vs)
+{
+ replica_updatedn_list_delete(list, NULL); /* delete all values */
+ replica_updatedn_list_add(list, vs);
+}
+
+/* if vs is given, delete only those values - otherwise, delete all values */
+void
+replica_updatedn_list_delete(ReplicaUpdateDNList list, const Slapi_ValueSet *vs)
+{
+ PLHashTable *hash = list;
+ if (!vs || slapi_valueset_count(vs) == 0) { /* just delete everything */
+ PL_HashTableEnumerateEntries(hash, replica_destroy_hash_entry, NULL);
+ } else {
+ Slapi_ValueSet *vs_nc = (Slapi_ValueSet *)vs; /* cast away const */
+ Slapi_Value *val = NULL;
+ int index = 0;
+ for (index = slapi_valueset_first_value(vs_nc, &val); val;
+ index = slapi_valueset_next_value(vs_nc, index, &val)) {
+ Slapi_DN *dn = slapi_sdn_new_dn_byval(slapi_value_get_string(val));
+ /* locate object */
+ Slapi_DN *deldn = (Slapi_DN *)PL_HashTableLookup(hash, slapi_sdn_get_ndn(dn));
+ if (deldn == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "replica_updatedn_list_delete: "
+ "update DN with value (%s) is not in the update DN list.\n",
+ slapi_sdn_get_ndn(dn));
+ } else {
+ /* remove from hash */
+ PL_HashTableRemove(hash, slapi_sdn_get_ndn(dn));
+ /* free the pointer */
+ slapi_sdn_free(&deldn);
+ }
+ /* free the temp dn */
+ slapi_sdn_free(&dn);
+ }
+ }
+
+ return;
+}
+
+void
+replica_updatedn_list_add(ReplicaUpdateDNList list, const Slapi_ValueSet *vs)
+{
+ PLHashTable *hash = list;
+ Slapi_ValueSet *vs_nc = (Slapi_ValueSet *)vs; /* cast away const */
+ Slapi_Value *val = NULL;
+ int index = 0;
+
+ PR_ASSERT(list && vs);
+
+ for (index = slapi_valueset_first_value(vs_nc, &val); val;
+ index = slapi_valueset_next_value(vs_nc, index, &val)) {
+ Slapi_DN *dn = slapi_sdn_new_dn_byval(slapi_value_get_string(val));
+ const char *ndn = slapi_sdn_get_ndn(dn);
+
+ /* make sure that the name is unique */
+ if (PL_HashTableLookup(hash, ndn) != NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "replica_updatedn_list_add: "
+ "update DN with value (%s) already in the update DN list\n",
+ ndn);
+ slapi_sdn_free(&dn);
+ } else {
+ PL_HashTableAdd(hash, ndn, dn);
+ }
+ }
+
+ return;
+}
+
+PRBool
+replica_updatedn_list_ismember(ReplicaUpdateDNList list, const Slapi_DN *dn)
+{
+ PLHashTable *hash = list;
+ PRBool ret = PR_FALSE;
+
+ const char *ndn = slapi_sdn_get_ndn(dn);
+
+ /* Bug 605169 - null ndn would cause core dump */
+ if ( ndn ) {
+ ret = (PRBool)PL_HashTableLookupConst(hash, ndn);
+ }
+
+ return ret;
+}
+
+struct list_to_string_data {
+ char *string;
+ const char *delimiter;
+};
+
+static int
+convert_to_string(Slapi_DN *dn, void *arg)
+{
+ struct list_to_string_data *data = (struct list_to_string_data *)arg;
+ int newlen = strlen(slapi_sdn_get_dn(dn)) + strlen(data->delimiter) + 1;
+ if (data->string) {
+ newlen += strlen(data->string);
+ data->string = slapi_ch_realloc(data->string, newlen);
+ } else {
+ data->string = slapi_ch_calloc(1, newlen);
+ }
+ strcat(data->string, slapi_sdn_get_dn(dn));
+ strcat(data->string, data->delimiter);
+
+ return 1;
+}
+
+/* caller must slapi_ch_free_string the returned string */
+char *
+replica_updatedn_list_to_string(ReplicaUpdateDNList list, const char *delimiter)
+{
+ struct list_to_string_data data;
+ data.string = NULL;
+ data.delimiter = delimiter;
+ replica_updatedn_list_enumerate(list, convert_to_string, (void *)&data);
+ return data.string;
+}
+
+void
+replica_updatedn_list_enumerate(ReplicaUpdateDNList list, FNEnumDN fn, void *arg)
+{
+ PLHashTable *hash = list;
+ struct repl_enum_data data;
+
+ PR_ASSERT (fn);
+
+ data.fn = fn;
+ data.arg = arg;
+
+ PL_HashTableEnumerateEntries(hash, updatedn_list_enumerate, &data);
+}
+
+/* Helper functions */
+
+/* this function called for each hash node during hash destruction */
+static PRIntn
+replica_destroy_hash_entry(PLHashEntry *he, PRIntn index, void *arg)
+{
+ Slapi_DN *dn = NULL;
+
+ if (he == NULL)
+ return HT_ENUMERATE_NEXT;
+
+ dn = (Slapi_DN *)he->value;
+ PR_ASSERT (dn);
+
+ slapi_sdn_free(&dn);
+
+ return HT_ENUMERATE_REMOVE;
+}
+
+static PRIntn
+updatedn_list_enumerate(PLHashEntry *he, PRIntn index, void *hash_data)
+{
+ Slapi_DN *dn = NULL;
+ struct repl_enum_data *data = hash_data;
+
+ dn = (Slapi_DN*)he->value;
+ PR_ASSERT (dn);
+
+ data->fn(dn, data->arg);
+
+ return HT_ENUMERATE_NEXT;
+}
diff --git a/ldap/servers/plugins/replication/repl_add.c b/ldap/servers/plugins/replication/repl_add.c
new file mode 100644
index 00000000..3d47c1db
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl_add.c
@@ -0,0 +1,30 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#include "slapi-plugin.h"
+#include "repl.h"
+
+
+/* Add Operation Plugin Functions for legacy replication plugin */
+
+int
+legacy_preop_add( Slapi_PBlock *pb )
+{
+ return legacy_preop( pb, "legacy_preop_add", OP_ADD );
+}
+
+int
+legacy_bepreop_add( Slapi_PBlock *pb )
+{
+ int rc= 0; /* OK */
+ return rc;
+}
+
+int
+legacy_postop_add( Slapi_PBlock *pb )
+{
+ return legacy_postop( pb, "legacy_postop_add", OP_ADD );
+}
diff --git a/ldap/servers/plugins/replication/repl_bind.c b/ldap/servers/plugins/replication/repl_bind.c
new file mode 100644
index 00000000..e317e311
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl_bind.c
@@ -0,0 +1,48 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+
+#include "slapi-plugin.h"
+#include "repl.h"
+#include "repl5.h"
+
+
+int
+legacy_preop_bind( Slapi_PBlock *pb )
+{
+ int return_value = 0;
+ char *dn = NULL;
+ struct berval *cred = NULL;
+ int method;
+ int one = 1;
+
+ slapi_pblock_get(pb, SLAPI_BIND_METHOD, &method);
+ slapi_pblock_get(pb, SLAPI_BIND_TARGET, &dn);
+ slapi_pblock_get(pb, SLAPI_BIND_CREDENTIALS, &cred);
+
+ if (LDAP_AUTH_SIMPLE == method)
+ {
+ if (legacy_consumer_is_replicationdn(dn) && legacy_consumer_is_replicationpw(cred))
+ {
+ /* Successful bind as replicationdn */
+ void *conn = NULL;
+ consumer_connection_extension *connext = NULL;
+#ifdef DEBUG
+ slapi_log_error(SLAPI_LOG_REPL, REPLICATION_SUBSYSTEM, "legacy_preop_bind: begin\n");
+#endif
+ slapi_pblock_get( pb, SLAPI_CONNECTION, &conn );
+ connext = (consumer_connection_extension*) repl_con_get_ext (REPL_CON_EXT_CONN, conn);
+ if (NULL != connext)
+ {
+ connext->is_legacy_replication_dn = 1;
+ }
+ slapi_send_ldap_result(pb, LDAP_SUCCESS, NULL, NULL, 0, NULL);
+ return_value = 1; /* Prevent further processing in front end */
+ }
+ }
+ return return_value;
+
+}
diff --git a/ldap/servers/plugins/replication/repl_compare.c b/ldap/servers/plugins/replication/repl_compare.c
new file mode 100644
index 00000000..34f2b944
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl_compare.c
@@ -0,0 +1,34 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#include "slapi-plugin.h"
+#include "repl.h"
+
+int
+legacy_preop_compare( Slapi_PBlock *pb )
+{
+ int is_replicated_operation = 0;
+ char *compare_base = NULL;
+ struct berval **referral = NULL;
+ int return_code = 0;
+ Slapi_DN *basesdn;
+
+ slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_replicated_operation);
+ slapi_pblock_get(pb, SLAPI_COMPARE_TARGET, &compare_base);
+ basesdn= slapi_sdn_new_dn_byref(compare_base);
+ referral = get_data_source(pb, basesdn, 1, NULL);
+ slapi_sdn_free(&basesdn);
+ if (NULL != referral && !is_replicated_operation)
+ {
+ /*
+ * There is a copyingFrom in this entry or an ancestor.
+ * Return a referral to the supplier, and we're all done.
+ */
+ slapi_send_ldap_result(pb, LDAP_REFERRAL, NULL, NULL, 0, referral);
+ return_code = 1; /* return 1 to prevent further search processing */
+ }
+ return return_code;
+}
diff --git a/ldap/servers/plugins/replication/repl_connext.c b/ldap/servers/plugins/replication/repl_connext.c
new file mode 100644
index 00000000..8b0c0551
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl_connext.c
@@ -0,0 +1,91 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* repl_connext.c - replication extension to the Connection object
+ */
+
+
+#include "repl.h"
+#include "repl5.h"
+
+
+/* ***** Supplier side ***** */
+
+/* NOT NEEDED YET */
+
+/* ***** Consumer side ***** */
+
+/* consumer connection extension constructor */
+void* consumer_connection_extension_constructor (void *object, void *parent)
+{
+ consumer_connection_extension *ext = (consumer_connection_extension*) slapi_ch_malloc (sizeof (consumer_connection_extension));
+ if (ext == NULL)
+ {
+ slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "unable to create replication consumer connection extension - out of memory\n" );
+ }
+ else
+ {
+ ext->is_legacy_replication_dn= 0;
+ ext->repl_protocol_version = REPL_PROTOCOL_UNKNOWN;
+ ext->replica_acquired = NULL;
+ ext->isreplicationsession= 0;
+ ext->supplier_ruv = NULL;
+ ext->connection = NULL;
+ }
+
+ return ext;
+}
+
+/* consumer connection extension destructor */
+void consumer_connection_extension_destructor (void *ext, void *object, void *parent)
+{
+ int connid = 0;
+ if (ext)
+ {
+ /* Check to see if this replication session has acquired
+ * a replica. If so, release it here.
+ */
+ consumer_connection_extension *connext = (consumer_connection_extension *)ext;
+ if (NULL != connext->replica_acquired)
+ {
+ Replica *r = object_get_data ((Object*)connext->replica_acquired);
+ /* If a total update was in progress, abort it */
+ if (REPL_PROTOCOL_50_TOTALUPDATE == connext->repl_protocol_version)
+ {
+ Slapi_PBlock *pb = slapi_pblock_new();
+ const Slapi_DN *repl_root_sdn = replica_get_root(r);
+ PR_ASSERT(NULL != repl_root_sdn);
+ if (NULL != repl_root_sdn)
+ {
+ slapi_pblock_set(pb, SLAPI_CONNECTION, connext->connection);
+ slapi_pblock_set(pb, SLAPI_TARGET_DN, (void*)slapi_sdn_get_dn(repl_root_sdn));
+ slapi_pblock_get(pb, SLAPI_CONN_ID, &connid);
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "Aborting total update in progress for replicated "
+ "area %s connid=%d\n", slapi_sdn_get_dn(repl_root_sdn),
+ connid);
+ slapi_stop_bulk_import(pb);
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "consumer_connection_extension_destructor: can't determine root "
+ "of replicated area.\n");
+ }
+ slapi_pblock_destroy(pb);
+ }
+ replica_relinquish_exclusive_access(r, connid, -1);
+ object_release ((Object*)connext->replica_acquired);
+ connext->replica_acquired = NULL;
+ }
+
+ if (connext->supplier_ruv)
+ {
+ ruv_destroy ((RUV **)&connext->supplier_ruv);
+ }
+ connext->connection = NULL;
+ slapi_ch_free((void **)&ext);
+ }
+}
diff --git a/ldap/servers/plugins/replication/repl_controls.c b/ldap/servers/plugins/replication/repl_controls.c
new file mode 100644
index 00000000..ae1cb119
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl_controls.c
@@ -0,0 +1,337 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#include "slapi-plugin.h"
+#include "repl5.h"
+#include "repl.h" /* For LDAP_CONTROL_REPL_MODRDN_EXTRAMODS */
+
+/*
+ * repl_controls.c - convenience functions for creating and
+ * decoding controls that implement 5.0-style replication
+ * protocol operations.
+ *
+ * TODO: Send modrdn mods with modrdn operation
+ * Fix ber_printf() and ber_scanf() format strings - some are
+ * the wrong types.
+ */
+
+/*
+ * Return a pointer to a NSDS50ReplUpdateInfoControl.
+ * The control looks like this:
+ *
+ * NSDS50ReplUpdateInfoControl ::= SEQUENCE {
+ * uuid OCTET STRING,
+ * csn OCTET STRING,
+ * OPTIONAL [new]superior-uuid OCTET STRING
+ * OPTIONAL modrdn_mods XXXggood WHAT TYPE???
+ * }
+ */
+int
+create_NSDS50ReplUpdateInfoControl(const char *uuid,
+ const char *superior_uuid, const CSN *csn,
+ LDAPMod **modrdn_mods, LDAPControl **ctrlp)
+{
+ int retval;
+ BerElement *tmp_bere = NULL;
+ struct berval tmpval = {0};
+ char csn_str[CSN_STRSIZE];
+
+ if (NULL == ctrlp)
+ {
+ retval = LDAP_PARAM_ERROR;
+ goto loser;
+ }
+ else
+ {
+ if ((tmp_bere = ber_alloc()) == NULL)
+ {
+ retval = LDAP_NO_MEMORY;
+ goto loser;
+ }
+ else
+ {
+ /* Stuff uuid and csn into BerElement */
+ if (ber_printf(tmp_bere, "{") == -1)
+ {
+ retval = LDAP_ENCODING_ERROR;
+ goto loser;
+ }
+
+ /* Stuff uuid of this entry into BerElement */
+ if (ber_printf(tmp_bere, "s", uuid) == -1)
+ {
+ retval = LDAP_ENCODING_ERROR;
+ goto loser;
+ }
+
+ /* Stuff csn of this change into BerElement */
+ csn_as_string(csn, PR_FALSE, csn_str);
+ if (ber_printf(tmp_bere, "s", csn_str) == -1)
+ {
+ retval = LDAP_ENCODING_ERROR;
+ goto loser;
+ }
+
+ /* If present, stuff uuid of parent entry into BerElement */
+ if (NULL != superior_uuid)
+ {
+ if (ber_printf(tmp_bere, "s", superior_uuid) == -1)
+ {
+ retval = LDAP_ENCODING_ERROR;
+ goto loser;
+ }
+ }
+
+ /* If present, add the modrdn mods */
+ if (NULL != modrdn_mods)
+ {
+ int i;
+ if (ber_printf(tmp_bere, "{" ) == -1)
+ {
+ retval = LDAP_ENCODING_ERROR;
+ goto loser;
+ }
+ /* for each modification to be performed... */
+ for (i = 0; NULL != modrdn_mods[i]; i++)
+ {
+ if (ber_printf(tmp_bere, "{e{s[V]}}",
+ modrdn_mods[i]->mod_op & ~LDAP_MOD_BVALUES,
+ modrdn_mods[i]->mod_type, modrdn_mods[i]->mod_bvalues ) == -1)
+ {
+ retval = LDAP_ENCODING_ERROR;
+ goto loser;
+ }
+ }
+ if (ber_printf(tmp_bere, "}") == -1)
+ {
+ retval = LDAP_ENCODING_ERROR;
+ goto loser;
+ }
+ }
+
+ /* Close the sequence */
+ if (ber_printf(tmp_bere, "}") == -1)
+ {
+ retval = LDAP_ENCODING_ERROR;
+ goto loser;
+ }
+
+ retval = slapi_build_control( REPL_NSDS50_UPDATE_INFO_CONTROL_OID,
+ tmp_bere, 1 /* is critical */, ctrlp);
+ }
+ }
+loser:
+ if (NULL != tmp_bere)
+ {
+ ber_free(tmp_bere, 1);
+ tmp_bere = NULL;
+ }
+ return retval;
+}
+
+
+/*
+ * Destroy a ReplUpdateInfoControl and set the pointer to NULL.
+ */
+void
+destroy_NSDS50ReplUpdateInfoControl(LDAPControl **ctrlp)
+{
+ if (NULL != ctrlp && NULL != *ctrlp)
+ {
+ ldap_control_free(*ctrlp);
+ *ctrlp = NULL;
+ }
+}
+
+
+
+
+/*
+ * Look through the array of controls. If an NSDS50ReplUpdateInfoControl
+ * is present, decode it and return pointers to the broken-out
+ * components. The caller is responsible for freeing pointers to
+ * the returned objects. The caller may indicate that it is not
+ * interested in any of the output parameters by passing NULL
+ * for that parameter.
+ *
+ * Returns 0 if the control is not present, 1 if it is present, and
+ * -1 if an error occurs.
+ */
+int
+decode_NSDS50ReplUpdateInfoControl(LDAPControl **controlsp,
+ char **uuid, char **superior_uuid,
+ CSN **csn, LDAPMod ***modrdn_mods)
+{
+ struct berval *ctl_value = NULL;
+ int iscritical = 0;
+ int rc = -1;
+ struct berval uuid_val = {0};
+ struct berval superior_uuid_val = {0};
+ struct berval csn_val = {0};
+ BerElement *tmp_bere = NULL;
+ Slapi_Mods modrdn_smods;
+ PRBool got_modrdn_mods = PR_FALSE;
+ unsigned long len;
+
+ slapi_mods_init(&modrdn_smods, 4);
+ if (slapi_control_present(controlsp, REPL_NSDS50_UPDATE_INFO_CONTROL_OID,
+ &ctl_value, &iscritical))
+ {
+ if ((tmp_bere = ber_init(ctl_value)) == NULL)
+ {
+ rc = -1;
+ goto loser;
+ }
+ if (ber_scanf(tmp_bere, "{oo", &uuid_val, &csn_val) == -1)
+ {
+ rc = -1;
+ goto loser;
+ }
+ if (ber_peek_tag(tmp_bere, &len) == LBER_OCTETSTRING)
+ {
+ /* The optional superior_uuid is present */
+ if (ber_scanf(tmp_bere, "o", &superior_uuid_val) == -1)
+ {
+ rc = -1;
+ goto loser;
+ }
+ }
+ if (ber_peek_tag(tmp_bere, &len) == LBER_SEQUENCE)
+ {
+ unsigned long emtag, emlen;
+ char *emlast;
+
+ for ( emtag = ber_first_element( tmp_bere, &emlen, &emlast );
+ emtag != LBER_ERROR && emtag != LBER_END_OF_SEQORSET;
+ emtag = ber_next_element( tmp_bere, &emlen, emlast ))
+ {
+ struct berval **embvals;
+ long op;
+ char *type;
+ if ( ber_scanf( tmp_bere, "{i{a[V]}}", &op, &type, &embvals ) == LBER_ERROR )
+ {
+ rc = -1;
+ goto loser;
+ }
+ slapi_mods_add_modbvps(&modrdn_smods, op, type, embvals);
+ free( type );
+ ber_bvecfree( embvals );
+ }
+ got_modrdn_mods = PR_TRUE;
+ }
+ if (ber_scanf(tmp_bere, "}") == -1)
+ {
+ rc = -1;
+ goto loser;
+ }
+
+ if (NULL != uuid)
+ {
+ *uuid = slapi_ch_malloc(uuid_val.bv_len + 1);
+ strncpy(*uuid, uuid_val.bv_val, uuid_val.bv_len);
+ (*uuid)[uuid_val.bv_len] = '\0';
+ }
+
+ if (NULL != csn)
+ {
+ char *csnstr = slapi_ch_malloc(csn_val.bv_len + 1);
+ strncpy(csnstr, csn_val.bv_val, csn_val.bv_len);
+ csnstr[csn_val.bv_len] = '\0';
+ *csn = csn_new_by_string(csnstr);
+ slapi_ch_free((void **)&csnstr);
+ }
+
+ if (NULL != superior_uuid && NULL != superior_uuid_val.bv_val)
+ {
+ *superior_uuid = slapi_ch_malloc(superior_uuid_val.bv_len + 1);
+ strncpy(*superior_uuid, superior_uuid_val.bv_val,
+ superior_uuid_val.bv_len);
+ (*superior_uuid)[superior_uuid_val.bv_len] = '\0';
+ }
+
+ if (NULL != modrdn_mods && got_modrdn_mods)
+ {
+ *modrdn_mods = slapi_mods_get_ldapmods_passout(&modrdn_smods);
+ }
+ slapi_mods_done(&modrdn_smods);
+
+ rc = 1;
+ }
+ else
+ {
+ rc = 0;
+ }
+loser:
+ /* XXXggood free CSN here if allocated */
+
+ if (NULL != tmp_bere)
+ {
+ ber_free(tmp_bere, 1);
+ tmp_bere = NULL;
+ }
+ if (NULL != uuid_val.bv_val)
+ {
+ ldap_memfree(uuid_val.bv_val);
+ uuid_val.bv_val = NULL;
+ }
+ if (NULL != superior_uuid_val.bv_val)
+ {
+ ldap_memfree(superior_uuid_val.bv_val);
+ superior_uuid_val.bv_val = NULL;
+ }
+ if (NULL != csn_val.bv_val)
+ {
+ ldap_memfree(csn_val.bv_val);
+ csn_val.bv_val = NULL;
+ }
+ return rc;
+}
+
+
+
+void
+add_repl_control_mods( Slapi_PBlock *pb, Slapi_Mods *smods )
+{
+ struct berval *embvp;
+ LDAPControl **controls = NULL;
+
+ slapi_pblock_get( pb, SLAPI_REQCONTROLS, &controls);
+ if ( slapi_control_present( controls,
+ LDAP_CONTROL_REPL_MODRDN_EXTRAMODS,
+ &embvp, NULL ))
+ {
+ if ( embvp != NULL && embvp->bv_len > 0 && embvp->bv_val != NULL )
+ {
+ /* Parse the extramods stuff */
+ long op;
+ char *type;
+ unsigned long emlen;
+ unsigned long emtag;
+ char *emlast;
+ BerElement *ember = ber_init( embvp );
+ if ( ember != NULL )
+ {
+ for ( emtag = ber_first_element( ember, &emlen, &emlast );
+ emtag != LBER_ERROR && emtag != LBER_END_OF_SEQORSET;
+ emtag = ber_next_element( ember, &emlen, emlast ))
+ {
+ struct berval **embvals;
+ if ( ber_scanf( ember, "{i{a[V]}}", &op, &type, &embvals ) == LBER_ERROR )
+ {
+ continue;
+ /* GGOODREPL I suspect this will cause two sets of lastmods attr values
+ to end up in the entry. We need to remove the old ones.
+ */
+ }
+ slapi_mods_add_modbvps( smods, op, type, embvals);
+ free( type );
+ ber_bvecfree( embvals );
+ }
+ }
+ ber_free( ember, 1 );
+ }
+ }
+}
diff --git a/ldap/servers/plugins/replication/repl_delete.c b/ldap/servers/plugins/replication/repl_delete.c
new file mode 100644
index 00000000..6f8e07df
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl_delete.c
@@ -0,0 +1,26 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#include "slapi-plugin.h"
+#include "repl.h"
+
+int
+legacy_preop_delete( Slapi_PBlock *pb )
+{
+ return legacy_preop(pb, "legacy_preop_delete", OP_DELETE);
+}
+
+int
+legacy_bepreop_delete( Slapi_PBlock *pb )
+{
+ return 0; /* OK */
+}
+
+int
+legacy_postop_delete( Slapi_PBlock *pb )
+{
+ return legacy_postop(pb, "legacy_preop_delete", OP_DELETE);
+}
diff --git a/ldap/servers/plugins/replication/repl_entry.c b/ldap/servers/plugins/replication/repl_entry.c
new file mode 100644
index 00000000..83bf2857
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl_entry.c
@@ -0,0 +1,38 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#include "slapi-plugin.h"
+#include "repl.h"
+
+static int dumping_to_ldif= 0;
+static int doing_replica_init= 0;
+static char **include_suffix= NULL;
+
+/*
+ * This is passed the slapd command line arguments.
+ */
+void
+repl_entry_init(int argc, char** argv)
+{
+ int i;
+ for(i=1;i<argc;i++)
+ {
+ if(strcmp(argv[i],"db2ldif")==0)
+ {
+ dumping_to_ldif= 1;
+ }
+ if(strcmp(argv[i],"-r")==0)
+ {
+ doing_replica_init= 1;
+ }
+ if(strcmp(argv[i],"-s")==0)
+ {
+ char *s= slapi_dn_normalize ( slapi_ch_strdup(argv[i+1]) );
+ charray_add(&include_suffix,s);
+ i++;
+ }
+ }
+}
diff --git a/ldap/servers/plugins/replication/repl_ext.c b/ldap/servers/plugins/replication/repl_ext.c
new file mode 100644
index 00000000..4ad28726
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl_ext.c
@@ -0,0 +1,113 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* repl_ext.c - manages operation extensions created by the
+ * replication system
+ */
+
+
+#include "repl.h"
+
+/* structure with information for each extension */
+typedef struct repl_ext
+{
+ char *object_name; /* name of the object extended */
+ int object_type; /* handle to the extended object */
+ int handle; /* extension handle */
+} repl_ext;
+
+/* ----------------------------- Supplier ----------------------------- */
+
+static repl_ext repl_sup_ext_list [REPL_EXT_ALL];
+
+/* initializes replication extensions */
+void repl_sup_init_ext ()
+{
+ int rc;
+
+ /* populate the extension list */
+ repl_sup_ext_list[REPL_SUP_EXT_OP].object_name = SLAPI_EXT_OPERATION;
+
+ rc = slapi_register_object_extension(repl_plugin_name,
+ SLAPI_EXT_OPERATION,
+ supplier_operation_extension_constructor,
+ supplier_operation_extension_destructor,
+ &repl_sup_ext_list[REPL_SUP_EXT_OP].object_type,
+ &repl_sup_ext_list[REPL_SUP_EXT_OP].handle);
+
+ if(rc!=0)
+ {
+ PR_ASSERT(0); /* JCMREPL Argh */
+ }
+}
+
+void* repl_sup_get_ext (ext_type type, void *object)
+{
+ /* find the requested extension */
+ repl_ext ext = repl_sup_ext_list [type];
+
+ void* data = slapi_get_object_extension(ext.object_type, object, ext.handle);
+
+ return data;
+}
+
+/* ----------------------------- Consumer ----------------------------- */
+
+static repl_ext repl_con_ext_list [REPL_EXT_ALL];
+
+/* initializes replication extensions */
+void repl_con_init_ext ()
+{
+ int rc;
+
+ /* populate the extension list */
+ repl_con_ext_list[REPL_CON_EXT_OP].object_name = SLAPI_EXT_OPERATION;
+ rc = slapi_register_object_extension(repl_plugin_name,
+ SLAPI_EXT_OPERATION,
+ consumer_operation_extension_constructor,
+ consumer_operation_extension_destructor,
+ &repl_con_ext_list[REPL_CON_EXT_OP].object_type,
+ &repl_con_ext_list[REPL_CON_EXT_OP].handle);
+ if(rc!=0)
+ {
+ PR_ASSERT(0); /* JCMREPL Argh */
+ }
+
+ repl_con_ext_list[REPL_CON_EXT_CONN].object_name = SLAPI_EXT_CONNECTION;
+ rc = slapi_register_object_extension(repl_plugin_name,
+ SLAPI_EXT_CONNECTION,
+ consumer_connection_extension_constructor,
+ consumer_connection_extension_destructor,
+ &repl_con_ext_list[REPL_CON_EXT_CONN].object_type,
+ &repl_con_ext_list[REPL_CON_EXT_CONN].handle);
+ if(rc!=0)
+ {
+ PR_ASSERT(0); /* JCMREPL Argh */
+ }
+
+ repl_con_ext_list[REPL_CON_EXT_MTNODE].object_name = SLAPI_EXT_MTNODE;
+ rc = slapi_register_object_extension(repl_plugin_name,
+ SLAPI_EXT_MTNODE,
+ multimaster_mtnode_extension_constructor,
+ multimaster_mtnode_extension_destructor,
+ &repl_con_ext_list[REPL_CON_EXT_MTNODE].object_type,
+ &repl_con_ext_list[REPL_CON_EXT_MTNODE].handle);
+ if(rc!=0)
+ {
+ PR_ASSERT(0); /* JCMREPL Argh */
+ }
+}
+
+void* repl_con_get_ext (ext_type type, void *object)
+{
+ /* find the requested extension */
+ repl_ext ext = repl_con_ext_list [type];
+
+ void* data = slapi_get_object_extension(ext.object_type, object, ext.handle);
+
+ return data;
+}
+
+
diff --git a/ldap/servers/plugins/replication/repl_extop.c b/ldap/servers/plugins/replication/repl_extop.c
new file mode 100644
index 00000000..b13ad6ac
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl_extop.c
@@ -0,0 +1,1134 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#include "slapi-plugin.h"
+#include "repl.h"
+#include "repl5.h"
+#include "repl5_prot_private.h"
+#include "cl5_api.h"
+
+
+/*
+ * repl_extop.c - there are two types of functions in this file:
+ * - Code that implements an extended operation plugin.
+ * The replication DLL arranges for this code to
+ * be called when a StartNSDS50ReplicationRequest
+ * or an EndNSDS50ReplicationRequest extended operation
+ * is received.
+ * - Code that sends extended operations on an already-
+ * established client connection.
+ *
+ * The requestValue portion of the StartNSDS50ReplicationRequest
+ * looks like this:
+ *
+ * requestValue ::= SEQUENCE {
+ * replProtocolOID LDAPOID,
+ * replicatedTree LDAPDN,
+ supplierRUV OCTET STRING
+ * referralURLs SET of LDAPURL OPTIONAL
+ * csn OCTET STRING OPTIONAL
+ * }
+ *
+ */
+static int check_replica_id_uniqueness(Replica *replica, RUV *supplier_ruv);
+
+static int
+encode_ruv (BerElement *ber, const RUV *ruv)
+{
+ int rc = LDAP_SUCCESS;
+ struct berval **bvals = NULL;
+
+ PR_ASSERT (ber);
+ PR_ASSERT (ruv);
+
+ if (ruv_to_bervals(ruv, &bvals) != 0)
+ {
+ rc = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+
+ if (ber_printf(ber, "[V]", bvals) == -1)
+ {
+ rc = LDAP_ENCODING_ERROR;
+ goto done;
+ }
+
+ rc = LDAP_SUCCESS;
+
+done:
+ if (bvals)
+ ber_bvecfree (bvals);
+
+ return rc;
+}
+
+static struct berval *
+create_NSDS50ReplicationExtopPayload(const char *protocol_oid,
+ const char *repl_root, char **extra_referrals, CSN *csn,
+ int send_end)
+{
+ struct berval *req_data = NULL;
+ BerElement *tmp_bere = NULL;
+ int rc = 0;
+ const char *csnstr = NULL;
+ Object *repl_obj, *ruv_obj = NULL;
+ Replica *repl;
+ RUV *ruv;
+ Slapi_DN *sdn;
+
+ PR_ASSERT(protocol_oid != NULL || send_end);
+ PR_ASSERT(repl_root != NULL);
+
+ /* Create the request data */
+
+ if ((tmp_bere = der_alloc()) == NULL)
+ {
+ rc = LDAP_ENCODING_ERROR;
+ goto loser;
+ }
+ if (!send_end)
+ {
+ if (ber_printf(tmp_bere, "{ss", protocol_oid, repl_root) == -1)
+ {
+ rc = LDAP_ENCODING_ERROR;
+ goto loser;
+ }
+ }
+ else
+ {
+ if (ber_printf(tmp_bere, "{s", repl_root) == -1)
+ {
+ rc = LDAP_ENCODING_ERROR;
+ goto loser;
+ }
+ }
+
+ sdn = slapi_sdn_new_dn_byref(repl_root);
+ repl_obj = replica_get_replica_from_dn (sdn);
+ if (repl_obj == NULL)
+ {
+ rc = LDAP_OPERATIONS_ERROR;
+ goto loser;
+ }
+
+ repl = (Replica*)object_get_data (repl_obj);
+ PR_ASSERT (repl);
+ ruv_obj = replica_get_ruv (repl);
+ if (ruv_obj == NULL)
+ {
+ rc = LDAP_OPERATIONS_ERROR;
+ goto loser;
+ }
+ ruv = object_get_data(ruv_obj);
+ PR_ASSERT(ruv);
+
+ /* send supplier's ruv so that consumer can build its own referrals.
+ In case of total protocol, it is also used as consumer's ruv once
+ protocol successfully completes */
+ /* We need to encode and send each time the local ruv in case we have changed it */
+ rc = encode_ruv (tmp_bere, ruv);
+ if (rc != 0)
+ {
+ goto loser;
+ }
+
+ if (!send_end)
+ {
+ char s[CSN_STRSIZE];
+ ReplicaId rid;
+ char *local_replica_referral[2] = {0};
+ char **referrals_to_send = NULL;
+ /* Add the referral URL(s), if present */
+ rid = replica_get_rid(repl);
+ if (!ruv_contains_replica(ruv, rid))
+ {
+ /*
+ * In the event that there is no RUV component for this replica (e.g.
+ * if the database was just loaded from LDIF and no local CSNs have been
+ * generated), then we need to explicitly add this server to the list
+ * of referrals, since it wouldn't have been sent with the RUV.
+ */
+ local_replica_referral[0] = (char *)multimaster_get_local_purl(); /* XXXggood had to cast away const */
+ }
+ charray_merge(&referrals_to_send, extra_referrals, 0);
+ charray_merge(&referrals_to_send, local_replica_referral, 0);
+ if (NULL != referrals_to_send)
+ {
+ if (ber_printf(tmp_bere, "[v]", referrals_to_send) == -1)
+ {
+ rc = LDAP_ENCODING_ERROR;
+ goto loser;
+ }
+ slapi_ch_free((void **)&referrals_to_send);
+ }
+ /* Add the CSN */
+ PR_ASSERT(NULL != csn);
+ if (ber_printf(tmp_bere, "s", csnstr = csn_as_string(csn,PR_FALSE,s)) == -1)
+ {
+ rc = LDAP_ENCODING_ERROR;
+ goto loser;
+ }
+ }
+
+ if (ber_printf(tmp_bere, "}") == -1)
+ {
+ rc = LDAP_ENCODING_ERROR;
+ goto loser;
+ }
+
+ if (ber_flatten(tmp_bere, &req_data) == -1)
+ {
+ rc = LDAP_LOCAL_ERROR;
+ goto loser;
+ }
+ /* Success */
+ goto done;
+
+loser:
+ /* Free stuff we allocated */
+ if (NULL != req_data)
+ {
+ ber_bvfree(req_data); req_data = NULL;
+ }
+
+done:
+ if (NULL != tmp_bere)
+ {
+ ber_free(tmp_bere, 1); tmp_bere = NULL;
+ }
+ if (NULL != sdn)
+ {
+ slapi_sdn_free (&sdn); /* Put on stack instead of allocating? */
+ }
+ if (NULL != repl_obj)
+ {
+ object_release (repl_obj);
+ }
+ if (NULL != ruv_obj)
+ {
+ object_release (ruv_obj);
+ }
+ return req_data;
+}
+
+
+struct berval *
+NSDS50StartReplicationRequest_new(const char *protocol_oid,
+ const char *repl_root, char **extra_referrals, CSN *csn)
+{
+ return(create_NSDS50ReplicationExtopPayload(protocol_oid,
+ repl_root, extra_referrals, csn, 0));
+}
+
+struct berval *
+NSDS50EndReplicationRequest_new(char *repl_root)
+{
+ return(create_NSDS50ReplicationExtopPayload(NULL, repl_root, NULL, NULL, 1));
+}
+
+static int
+decode_ruv (BerElement *ber, RUV **ruv)
+{
+ int rc = -1;
+ struct berval **bvals = NULL;
+
+ PR_ASSERT (ber && ruv);
+
+ if (ber_scanf(ber, "[V]", &bvals) == -1)
+ {
+ goto done;
+ }
+
+ if (ruv_init_from_bervals(bvals, ruv) != 0)
+ {
+ goto done;
+ }
+
+ rc = 0;
+done:
+ if (bvals)
+ ber_bvecfree (bvals);
+
+ return rc;
+}
+
+/*
+ * Decode an NSDS50 Start Replication Request extended
+ * operation. Returns 0 on success, -1 on decoding error.
+ * The caller is responsible for freeing protocol_oid,
+ * repl_root, referrals, and csn.
+ */
+static int
+decode_startrepl_extop(Slapi_PBlock *pb, char **protocol_oid, char **repl_root,
+ RUV **supplier_ruv, char ***extra_referrals, char **csnstr)
+{
+ char *extop_oid = NULL;
+ struct berval *extop_value = NULL;
+ BerElement *tmp_bere = NULL;
+ unsigned long len;
+ int rc = 0;
+
+ PR_ASSERT (pb && protocol_oid && repl_root && supplier_ruv && extra_referrals && csnstr);
+
+ *protocol_oid = NULL;
+ *repl_root = NULL;
+ *supplier_ruv = NULL;
+ *extra_referrals = NULL;
+ *csnstr = NULL;
+
+ slapi_pblock_get(pb, SLAPI_EXT_OP_REQ_OID, &extop_oid);
+ slapi_pblock_get(pb, SLAPI_EXT_OP_REQ_VALUE, &extop_value);
+
+ if (NULL == extop_oid ||
+ strcmp(extop_oid, REPL_START_NSDS50_REPLICATION_REQUEST_OID) != 0 ||
+ NULL == extop_value)
+ {
+ /* bogus */
+ rc = -1;
+ goto free_and_return;
+ }
+
+ if ((tmp_bere = ber_init(extop_value)) == NULL)
+ {
+ rc = -1;
+ goto free_and_return;
+ }
+ if (ber_scanf(tmp_bere, "{") == -1)
+ {
+ rc = -1;
+ goto free_and_return;
+ }
+ /* Get the required protocol OID and root of replicated subtree */
+ if (ber_get_stringa(tmp_bere, protocol_oid) == -1)
+ {
+ rc = -1;
+ goto free_and_return;
+ }
+ if (ber_get_stringa(tmp_bere, repl_root) == -1)
+ {
+ rc = -1;
+ goto free_and_return;
+ }
+
+ /* get supplier's ruv */
+ if (decode_ruv (tmp_bere, supplier_ruv) == -1)
+ {
+ rc = -1;
+ goto free_and_return;
+ }
+
+ /* Get the optional set of referral URLs */
+ if (ber_peek_tag(tmp_bere, &len) == LBER_SET)
+ {
+ if (ber_scanf(tmp_bere, "[v]", extra_referrals) == -1)
+ {
+ rc = -1;
+ goto free_and_return;
+ }
+ }
+ /* Get the optional CSN */
+ if (ber_peek_tag(tmp_bere, &len) == LBER_OCTETSTRING)
+ {
+ if (ber_get_stringa(tmp_bere, csnstr) == -1)
+ {
+ rc = -1;
+ goto free_and_return;
+ }
+ }
+ if (ber_scanf(tmp_bere, "}") == -1)
+ {
+ rc = -1;
+ goto free_and_return;
+ }
+
+free_and_return:
+ if (-1 == rc)
+ {
+ /* Free everything when error encountered */
+
+ /* slapi_ch_free accepts NULL pointer */
+ slapi_ch_free ((void**)protocol_oid);
+ slapi_ch_free ((void**)repl_root);
+ slapi_ch_free ((void **)extra_referrals);
+ slapi_ch_free ((void**)csnstr);
+
+ if (*supplier_ruv)
+ {
+ ruv_destroy (supplier_ruv);
+ }
+
+ }
+ if (NULL != tmp_bere)
+ {
+ ber_free(tmp_bere, 1);
+ tmp_bere = NULL;
+ }
+
+ return rc;
+}
+
+
+/*
+ * Decode an NSDS50 End Replication Request extended
+ * operation. Returns 0 on success, -1 on decoding error.
+ * The caller is responsible for freeing repl_root.
+ */
+static int
+decode_endrepl_extop(Slapi_PBlock *pb, char **repl_root)
+{
+ char *extop_oid = NULL;
+ struct berval *extop_value = NULL;
+ BerElement *tmp_bere = NULL;
+ int rc = 0;
+
+ slapi_pblock_get(pb, SLAPI_EXT_OP_REQ_OID, &extop_oid);
+ slapi_pblock_get(pb, SLAPI_EXT_OP_REQ_VALUE, &extop_value);
+
+ if (NULL == extop_oid ||
+ strcmp(extop_oid, REPL_END_NSDS50_REPLICATION_REQUEST_OID) != 0 ||
+ NULL == extop_value)
+ {
+ /* bogus */
+ rc = -1;
+ goto free_and_return;
+ }
+
+ if ((tmp_bere = ber_init(extop_value)) == NULL)
+ {
+ rc = -1;
+ goto free_and_return;
+ }
+ if (ber_scanf(tmp_bere, "{") == -1)
+ {
+ rc = -1;
+ goto free_and_return;
+ }
+ /* Get the required root of replicated subtree */
+ if (ber_get_stringa(tmp_bere, repl_root) == -1)
+ {
+ rc = -1;
+ goto free_and_return;
+ }
+ if (ber_scanf(tmp_bere, "}") == -1)
+ {
+ rc = -1;
+ goto free_and_return;
+ }
+
+free_and_return:
+ if (NULL != tmp_bere)
+ {
+ ber_free(tmp_bere, 1);
+ tmp_bere = NULL;
+ }
+
+ return rc;
+}
+
+
+
+
+/*
+ * Decode an NSDS50ReplicationResponse extended response.
+ * The extended response just contains a sequence that contains:
+ * 1) An integer response code
+ * 2) An optional array of bervals representing the consumer
+ * replica's update vector
+ * Returns 0 on success, or -1 if the response could not be parsed.
+ */
+int
+decode_repl_ext_response(struct berval *data, int *response_code,
+ struct berval ***ruv_bervals)
+{
+ BerElement *tmp_bere = NULL;
+ int return_value = 0;
+
+ PR_ASSERT(NULL != response_code);
+ PR_ASSERT(NULL != ruv_bervals);
+
+ if (NULL == data || NULL == response_code || NULL == ruv_bervals)
+ {
+ return_value = -1;
+ }
+ else
+ {
+ unsigned long len, tag = 0;
+ long temp_response_code = 0;
+ *ruv_bervals = NULL;
+ if ((tmp_bere = ber_init(data)) == NULL)
+ {
+ return_value = -1;
+ }
+ else if (ber_scanf(tmp_bere, "{e", &temp_response_code) == -1)
+ {
+ return_value = -1;
+ }
+ else if ((tag = ber_peek_tag(tmp_bere, &len)) == LBER_SEQUENCE)
+ {
+ if (ber_scanf(tmp_bere, "{V}}", ruv_bervals) == -1)
+ {
+ return_value = -1;
+ }
+ } else if (ber_scanf(tmp_bere, "}") == -1)
+ {
+ return_value = -1;
+ }
+ *response_code = (int)temp_response_code;
+ }
+ if (0 != return_value)
+ {
+ if (NULL != *ruv_bervals)
+ {
+ ber_bvecfree(*ruv_bervals);
+ }
+ }
+ if (NULL != tmp_bere)
+ {
+ ber_free(tmp_bere, 1); tmp_bere = NULL;
+ }
+ return return_value;
+}
+
+
+/*
+ * This plugin entry point is called whenever a
+ * StartNSDS50ReplicationRequest is received.
+ */
+int
+multimaster_extop_StartNSDS50ReplicationRequest(Slapi_PBlock *pb)
+{
+ int return_value = SLAPI_PLUGIN_EXTENDED_NOT_HANDLED;
+ int response = 0;
+ int rc = 0;
+ BerElement *resp_bere = NULL;
+ struct berval *resp_bval = NULL;
+ char *protocol_oid = NULL;
+ char *repl_root = NULL;
+ Slapi_DN *repl_root_sdn = NULL;
+ char **referrals = NULL;
+ Object *replica_object = NULL;
+ Replica *replica = NULL;
+ void *conn;
+ consumer_connection_extension *connext = NULL;
+ CSN *mycsn = NULL;
+ char *replicacsnstr = NULL;
+ CSN *replicacsn = NULL;
+ int zero = 0;
+ int one = 1;
+ RUV *ruv = NULL;
+ struct berval **ruv_bervals = NULL;
+ CSNGen *gen = NULL;
+ Object *gen_obj = NULL;
+ Slapi_DN *bind_sdn = NULL;
+ char *bind_dn = NULL;
+ Object *ruv_object = NULL;
+ RUV *supplier_ruv = NULL;
+ int connid, opid;
+ PRBool isInc = PR_FALSE; /* true if incremental update */
+ char *locking_purl = NULL; /* the supplier contacting us */
+ char *current_purl = NULL; /* the supplier which already has exclusive access */
+ char locking_session[24];
+
+ /* Decode the extended operation */
+ if (decode_startrepl_extop(pb, &protocol_oid, &repl_root, &supplier_ruv,
+ &referrals, &replicacsnstr) == -1)
+ {
+ response = NSDS50_REPL_DECODING_ERROR;
+ goto send_response;
+ }
+ if (NULL == protocol_oid || NULL == repl_root || NULL == replicacsnstr)
+ {
+ response = NSDS50_REPL_DECODING_ERROR;
+ goto send_response;
+ }
+
+ connid = 0;
+ slapi_pblock_get(pb, SLAPI_CONN_ID, &connid);
+ opid = 0;
+ slapi_pblock_get(pb, SLAPI_OPERATION_ID, &opid);
+
+ /*
+ * Get a hold of the connection extension object and
+ * make sure it's there.
+ */
+ slapi_pblock_get(pb, SLAPI_CONNECTION, &conn);
+ connext = (consumer_connection_extension *)repl_con_get_ext(
+ REPL_CON_EXT_CONN, conn);
+ if (NULL == connext)
+ {
+ /* Something bad happened. Don't go any further */
+ response = NSDS50_REPL_INTERNAL_ERROR;
+ goto send_response;
+ }
+
+ /* Verify that we know about this replication protocol OID */
+ if (strcmp(protocol_oid, REPL_NSDS50_INCREMENTAL_PROTOCOL_OID) == 0)
+ {
+ /* Stash info that this is an incremental update session */
+ connext->repl_protocol_version = REPL_PROTOCOL_50_INCREMENTAL;
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "conn=%d op=%d repl=\"%s\": Begin incremental protocol\n",
+ connid, opid, repl_root);
+ isInc = PR_TRUE;
+ }
+ else if (strcmp(protocol_oid, REPL_NSDS50_TOTAL_PROTOCOL_OID) == 0)
+ {
+ /* Stash info that this is a total update session */
+ if (NULL != connext)
+ {
+ connext->repl_protocol_version = REPL_PROTOCOL_50_TOTALUPDATE;
+ }
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "conn=%d op=%d repl=\"%s\": Begin total protocol\n",
+ connid, opid, repl_root);
+ isInc = PR_FALSE;
+ }
+ else
+ {
+ /* Unknown replication protocol */
+ response = NSDS50_REPL_UNKNOWN_UPDATE_PROTOCOL;
+ goto send_response;
+ }
+
+ /* Verify that repl_root names a valid replicated area */
+ if ((repl_root_sdn = slapi_sdn_new_dn_byval(repl_root)) == NULL)
+ {
+ response = NSDS50_REPL_INTERNAL_ERROR;
+ goto send_response;
+ }
+
+ /* see if this replica is being configured and wait for it */
+ if (replica_is_being_configured(repl_root))
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "conn=%d op=%d replica=\"%s\": "
+ "Replica is being configured: try again later\n",
+ connid, opid, repl_root);
+ response = NSDS50_REPL_REPLICA_BUSY;
+ goto send_response;
+ }
+
+ replica_object = replica_get_replica_from_dn(repl_root_sdn);
+ if (NULL != replica_object)
+ {
+ replica = object_get_data(replica_object);
+ }
+ if (NULL == replica)
+ {
+ response = NSDS50_REPL_NO_SUCH_REPLICA;
+ goto send_response;
+ }
+
+ /* check that this replica is not a 4.0 consumer */
+ if (replica_is_legacy_consumer (replica))
+ {
+ response = NSDS50_REPL_LEGACY_CONSUMER;
+ goto send_response;
+ }
+
+ /* Check that bind dn is authorized to supply replication updates */
+ slapi_pblock_get(pb, SLAPI_CONN_DN, &bind_dn); /* bind_dn is allocated */
+ bind_sdn = slapi_sdn_new_dn_passin(bind_dn);
+ if (replica_is_updatedn(replica, bind_sdn) == PR_FALSE)
+ {
+ response = NSDS50_REPL_PERMISSION_DENIED;
+ goto send_response;
+ }
+
+ /* Check received CSN for clock skew */
+ gen_obj = replica_get_csngen(replica);
+ if (NULL != gen_obj)
+ {
+ gen = object_get_data(gen_obj);
+ if (NULL != gen)
+ {
+ if (csngen_new_csn(gen, &mycsn, PR_FALSE /* notify */) == CSN_SUCCESS)
+ {
+ replicacsn = csn_new_by_string(replicacsnstr);
+ if (NULL != replicacsn)
+ {
+ /* ONREPL - we used to manage clock skew here. However, csn generator
+ code already does it. The csngen also manages local skew caused by
+ system clock reset, so to keep it consistent, I removed code from here */
+ time_t diff = 0L;
+ diff = csn_time_difference(mycsn, replicacsn);
+ if (diff > 0)
+ {
+ /* update the state of the csn generator */
+ rc = csngen_adjust_time (gen, replicacsn);
+ if (rc == CSN_LIMIT_EXCEEDED) /* too much skew */
+ {
+ response = NSDS50_REPL_EXCESSIVE_CLOCK_SKEW;
+ goto send_response;
+ }
+ }
+ else if (diff <= 0)
+ {
+ /* Supplier's clock is behind ours */
+ /* XXXggood check if CSN smaller than purge point */
+ /* response = NSDS50_REPL_BELOW_PURGEPOINT; */
+ /* goto send_response; */
+ }
+ }
+ else
+ {
+ /* Oops, csnstr couldn't be converted */
+ response = NSDS50_REPL_INTERNAL_ERROR;
+ goto send_response;
+ }
+ }
+ else
+ {
+ /* Oops, csn generator failed */
+ response = NSDS50_REPL_INTERNAL_ERROR;
+ goto send_response;
+ }
+
+ /* update csn generator's state from the supplier's ruv */
+ rc = replica_update_csngen_state (replica, supplier_ruv); /* too much skew */
+ if (rc != 0)
+ {
+ response = NSDS50_REPL_EXCESSIVE_CLOCK_SKEW;
+ goto send_response;
+ }
+ }
+ else
+ {
+ /* Oops, no csn generator */
+ response = NSDS50_REPL_INTERNAL_ERROR;
+ goto send_response;
+ }
+ }
+ else
+ {
+ /* Oops, no csn generator object */
+ response = NSDS50_REPL_INTERNAL_ERROR;
+ goto send_response;
+ }
+
+ if (check_replica_id_uniqueness(replica, supplier_ruv) != 0){
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "conn=%d op=%d repl=\"%s\": "
+ "Replica has same replicaID %d as supplier\n",
+ connid, opid, repl_root, replica_get_rid(replica));
+ response = NSDS50_REPL_REPLICAID_ERROR;
+ goto send_response;
+ }
+
+ /* Attempt to acquire exclusive access to the replicated area */
+ /* Since partial URL is always the master, this locking_purl does not
+ * help us to know the true locker when it is a hub. Change to use
+ * the session's conn id and op id to identify the the supplier.
+ */
+ /* junkrc = ruv_get_first_id_and_purl(supplier_ruv, &junkrid, &locking_purl); */
+ sprintf(locking_session, "conn=%d id=%d", connid, opid);
+ locking_purl = &locking_session[0];
+ if (replica_get_exclusive_access(replica, &isInc, connid, opid,
+ locking_purl,
+ &current_purl) == PR_FALSE)
+ {
+ locking_purl = NULL; /* no dangling pointers */
+ response = NSDS50_REPL_REPLICA_BUSY;
+ goto send_response;
+ }
+ else
+ {
+ locking_purl = NULL; /* no dangling pointers */
+ /* Stick the replica object pointer in the connection extension */
+ connext->replica_acquired = (void *)replica_object;
+ replica_object = NULL;
+ }
+
+ /* If this is incremental protocol get replica's ruv to return to the supplier */
+ if (connext->repl_protocol_version == REPL_PROTOCOL_50_INCREMENTAL)
+ {
+ ruv_object = replica_get_ruv(replica);
+ if (NULL != ruv_object)
+ {
+ ruv = object_get_data(ruv_object);
+ (void)ruv_to_bervals(ruv, &ruv_bervals);
+ object_release(ruv_object);
+ }
+ }
+
+ /*
+ * Save the supplier ruv in the connection extension so it can
+ * either (a) be installed upon successful initialization (if this
+ * is a total update session) or used to update referral information
+ * for new replicas that show up in the supplier's RUV.
+ */
+ /*
+ * the supplier_ruv may have been set before, so free it here
+ * (in ruv_copy_and_destroy)
+ */
+ ruv_copy_and_destroy(&supplier_ruv, (RUV **)&connext->supplier_ruv);
+
+ if (connext->repl_protocol_version == REPL_PROTOCOL_50_INCREMENTAL)
+ {
+ /* The supplier ruv may have changed, so let's update the referrals */
+ consumer5_set_mapping_tree_state_for_replica(replica, connext->supplier_ruv);
+ }
+ else /* full protocol */
+ {
+ char *mtnstate = slapi_mtn_get_state(repl_root_sdn);
+ char **mtnreferral = slapi_mtn_get_referral(repl_root_sdn);
+
+ /* richm 20010831 - set the mapping tree to the referral state *before*
+ we invoke slapi_start_bulk_import - see bug 556992 -
+ slapi_start_bulk_import sets the database offline, if an operation comes
+ in while the database is offline but the mapping tree is not referring yet,
+ the server gets confused
+ */
+ /* During a total update we refer *all* operations */
+ repl_set_mtn_state_and_referrals(repl_root_sdn, STATE_REFERRAL,
+ connext->supplier_ruv, NULL, referrals);
+ /* LPREPL - check the return code.
+ * But what do we do if mapping tree could not be updated ? */
+
+ /* start the bulk import */
+ slapi_pblock_set (pb, SLAPI_TARGET_DN, repl_root);
+ rc = slapi_start_bulk_import (pb);
+ if (rc != LDAP_SUCCESS)
+ {
+ response = NSDS50_REPL_INTERNAL_ERROR;
+ /* reset the mapping tree state to what it was before
+ we tried to do the bulk import */
+ repl_set_mtn_state_and_referrals(repl_root_sdn, mtnstate,
+ NULL, NULL, mtnreferral);
+ slapi_ch_free_string(&mtnstate);
+ charray_free(mtnreferral);
+ mtnreferral = NULL;
+
+ goto send_response;
+ }
+ slapi_ch_free_string(&mtnstate);
+ charray_free(mtnreferral);
+ mtnreferral = NULL;
+ }
+
+ response = NSDS50_REPL_REPLICA_READY;
+ /* Set the "is replication session" flag in the connection extension */
+ slapi_pblock_set( pb, SLAPI_CONN_IS_REPLICATION_SESSION, &one );
+ connext->isreplicationsession = 1;
+ /* Save away the connection */
+ slapi_pblock_get(pb, SLAPI_CONNECTION, &connext->connection);
+
+send_response:
+ if (response != NSDS50_REPL_REPLICA_READY)
+ {
+ int resp_log_level = SLAPI_LOG_FATAL;
+ char purlstr[1024] = {0};
+ if (current_purl)
+ sprintf(purlstr, " locked by %s for %s update", current_purl,
+ isInc ? "incremental" : "total");
+
+ /* Don't log replica busy as errors - these are almost always not
+ errors - use the replication monitoring tools to determine if
+ a replica is not converging, then look for pathological replica
+ busy errors by turning on the replication log level */
+ if (response == NSDS50_REPL_REPLICA_BUSY) {
+ resp_log_level = SLAPI_LOG_REPL;
+ }
+
+ slapi_log_error (resp_log_level, repl_plugin_name,
+ "conn=%d op=%d replica=\"%s\": "
+ "Unable to acquire replica: error: %s%s\n",
+ connid, opid,
+ (replica ? slapi_sdn_get_dn(replica_get_root(replica)) : "unknown"),
+ protocol_response2string (response), purlstr);
+ }
+ /* Send the response */
+ if ((resp_bere = der_alloc()) == NULL)
+ {
+ /* ONREPL - not sure what we suppose to do here */
+ }
+ ber_printf(resp_bere, "{e", response);
+ if (NULL != ruv_bervals)
+ {
+ ber_printf(resp_bere, "{V}", ruv_bervals);
+ }
+ ber_printf(resp_bere, "}");
+ ber_flatten(resp_bere, &resp_bval);
+ slapi_pblock_set(pb, SLAPI_EXT_OP_RET_OID, REPL_NSDS50_REPLICATION_RESPONSE_OID);
+ slapi_pblock_set(pb, SLAPI_EXT_OP_RET_VALUE, resp_bval);
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "conn=%d op=%d repl=\"%s\": "
+ "StartNSDS50ReplicationRequest: response=%d rc=%d\n",
+ connid, opid, repl_root,
+ response, rc);
+ slapi_send_ldap_result(pb, LDAP_SUCCESS, NULL, NULL, 0, NULL);
+
+ return_value = SLAPI_PLUGIN_EXTENDED_SENT_RESULT;
+
+ slapi_ch_free_string(&current_purl);
+
+ /* protocol_oid */
+ /* slapi_ch_free accepts NULL pointer */
+ slapi_ch_free((void **)&protocol_oid);
+
+ /* repl_root */
+ slapi_ch_free((void **)&repl_root);
+
+ /* supplier's ruv */
+ if (supplier_ruv)
+ {
+ ruv_destroy (&supplier_ruv);
+ }
+ /* referrals */
+ slapi_ch_free((void **)&referrals);
+
+ /* replicacsnstr */
+ slapi_ch_free((void **)&replicacsnstr);
+
+ /* repl_root_sdn */
+ if (NULL != repl_root_sdn)
+ {
+ slapi_sdn_free(&repl_root_sdn);
+ }
+ if (NSDS50_REPL_REPLICA_READY != response)
+ {
+ /*
+ * Something went wrong, and we never told the other end that the
+ * replica had been acquired, so we'd better release it.
+ */
+ if (NULL != connext && NULL != connext->replica_acquired)
+ {
+ Object *r_obj = (Object*)connext->replica_acquired;
+ replica_relinquish_exclusive_access((Replica*)object_get_data (r_obj),
+ connid, opid);
+ }
+ /* Remove any flags that would indicate repl session in progress */
+ if (NULL != connext)
+ {
+ connext->repl_protocol_version = REPL_PROTOCOL_UNKNOWN;
+ connext->isreplicationsession = 0;
+ }
+ slapi_pblock_set( pb, SLAPI_CONN_IS_REPLICATION_SESSION, &zero );
+ }
+ /* Release reference to replica_object */
+ if (NULL != replica_object)
+ {
+ object_release(replica_object);
+ }
+ /* bind_sdn */
+ if (NULL != bind_sdn)
+ {
+ slapi_sdn_free(&bind_sdn);
+ }
+ /* Release reference to gen_obj */
+ if (NULL != gen_obj)
+ {
+ object_release(gen_obj);
+ }
+ /* mycsn */
+ if (NULL != mycsn)
+ {
+ csn_free(&mycsn);
+ }
+ /* replicacsn */
+ if (NULL != replicacsn)
+ {
+ csn_free(&replicacsn);
+ }
+ /* resp_bere */
+ if (NULL != resp_bere)
+ {
+ ber_free(resp_bere, 1);
+ }
+ /* resp_bval */
+ if (NULL != resp_bval)
+ {
+ ber_bvfree(resp_bval);
+ }
+ /* ruv_bervals */
+ if (NULL != ruv_bervals)
+ {
+ ber_bvecfree(ruv_bervals);
+ }
+
+ return return_value;
+}
+
+/*
+ * This plugin entry point is called whenever an
+ * EndNSDS50ReplicationRequest is received.
+ * XXXggood this code is not finished.
+ */
+int
+multimaster_extop_EndNSDS50ReplicationRequest(Slapi_PBlock *pb)
+{
+ int return_value = SLAPI_PLUGIN_EXTENDED_NOT_HANDLED;
+ char *repl_root = NULL;
+ BerElement *resp_bere = NULL;
+ struct berval *resp_bval = NULL;
+ int response;
+ void *conn;
+ consumer_connection_extension *connext = NULL;
+ int rc;
+ int connid=-1, opid=-1;
+
+ /* Decode the extended operation */
+ if (decode_endrepl_extop(pb, &repl_root) == -1)
+ {
+ response = NSDS50_REPL_DECODING_ERROR;
+ }
+ else
+ {
+
+ /* First, verify that the current connection is a replication session */
+ /* XXXggood - do we need to wait around for any pending updates to complete?
+ I suppose it's possible that the end request may arrive asynchronously, before
+ we're really done processing all the updates.
+ */
+ /* Get a hold of the connection extension object */
+ slapi_pblock_get(pb, SLAPI_CONNECTION, &conn);
+ connext = (consumer_connection_extension *)repl_con_get_ext(
+ REPL_CON_EXT_CONN, conn);
+ if (NULL != connext && NULL != connext->replica_acquired)
+ {
+ int zero= 0;
+ Replica *r = (Replica*)object_get_data ((Object*)connext->replica_acquired);
+
+ /* if this is total protocol we need to install suppliers ruv for the replica */
+ if (connext->repl_protocol_version == REPL_PROTOCOL_50_TOTALUPDATE)
+ {
+ /* We no longer need to refer all operations...
+ * and update the referrals on the mapping tree node
+ */
+ consumer5_set_mapping_tree_state_for_replica(r, NULL);
+
+ /* LPREPL - First we clear the total in progress flag
+ Like this we know it's a normal termination of import. This is required by
+ the replication function that responds to backend state change.
+ If the flag is not clear, the callback knows that replication should not be
+ enabled again */
+ replica_set_state_flag(r, REPLICA_TOTAL_IN_PROGRESS, PR_TRUE /* clear flag */);
+
+ slapi_pblock_set (pb, SLAPI_TARGET_DN, repl_root);
+ slapi_stop_bulk_import (pb);
+
+ /* ONREPL - this is a bit of a hack. Once bulk import is finished,
+ the replication function that responds to backend state change
+ will be called. That function normally do all ruv and changelog
+ processing. However, in the case of replica initalization, it
+ will not do the right thing because supplier does not send its
+ ruv tombstone to the consumer. So that's why we need to do the
+ second processing here.
+ The supplier does not send its RUV entry because it could be
+ more up to date then the data send to the consumer.
+ The best solution I think, would be to "fake" on the supplier
+ an entry that corresponds to the ruv sent to the consumer and then
+ send it as part of the data */
+
+ if (cl5GetState () == CL5_STATE_OPEN)
+ {
+ rc = cl5DeleteDBSync (connext->replica_acquired);
+ }
+
+ replica_set_ruv (r, connext->supplier_ruv);
+ connext->supplier_ruv = NULL;
+
+ /* if changelog is enabled, we need to log a dummy change for the
+ smallest csn in the new ruv, so that this replica ca supply
+ other servers.
+ */
+ if (cl5GetState () == CL5_STATE_OPEN)
+ {
+ replica_log_ruv_elements (r);
+ }
+
+ /* ONREPL code that dealt with new RUV, etc was moved into the code
+ that enables replication when a backend comes back online. This
+ code is called once the bulk import is finished */
+ }
+ else if (connext->repl_protocol_version == REPL_PROTOCOL_50_INCREMENTAL)
+ {
+ /* The ruv from the supplier may have changed. Report the change on the
+ consumer side */
+
+ replica_update_ruv_consumer(r, connext->supplier_ruv);
+ }
+
+ /* Relinquish control of the replica */
+ slapi_pblock_get (pb, SLAPI_OPERATION_ID, &opid);
+ if (opid) slapi_pblock_get (pb, SLAPI_CONN_ID, &connid);
+ replica_relinquish_exclusive_access(r, connid, opid);
+ object_release ((Object*)connext->replica_acquired);
+ connext->replica_acquired = NULL;
+ connext->isreplicationsession= 0;
+ slapi_pblock_set( pb, SLAPI_CONN_IS_REPLICATION_SESSION, &zero );
+ response = NSDS50_REPL_REPLICA_RELEASE_SUCCEEDED;
+ /* Outbound replication agreements need to all be restarted now */
+ /* XXXGGOOD RESTART REEPL AGREEMENTS */
+ }
+ }
+
+ /* Send the response code */
+ if ((resp_bere = der_alloc()) == NULL)
+ {
+ rc = LDAP_ENCODING_ERROR;
+ goto free_and_return;
+ }
+ ber_printf(resp_bere, "{e}", response);
+ ber_flatten(resp_bere, &resp_bval);
+ slapi_pblock_set(pb, SLAPI_EXT_OP_RET_OID, REPL_NSDS50_REPLICATION_RESPONSE_OID);
+ slapi_pblock_set(pb, SLAPI_EXT_OP_RET_VALUE, resp_bval);
+ slapi_send_ldap_result(pb, LDAP_SUCCESS, NULL, NULL, 0, NULL);
+
+ return_value = SLAPI_PLUGIN_EXTENDED_SENT_RESULT;
+
+free_and_return:
+ /* repl_root */
+ slapi_ch_free((void **)&repl_root);
+
+ /* BerElement */
+ if (NULL != resp_bere)
+ {
+ ber_free(resp_bere, 1);
+ }
+ /* response */
+ if (NULL != resp_bval)
+ {
+ ber_bvfree(resp_bval);
+ }
+
+ return return_value;
+}
+
+/*
+ * This plugin entry point is a noop entry
+ * point. It's used when registering extops that
+ * are only used as responses. We'll never receive
+ * one of those, unsolicited, but we still want to
+ * register them so they appear in the
+ * supportedextension attribute in the root DSE.
+ */
+int
+extop_noop(Slapi_PBlock *pb)
+{
+ return SLAPI_PLUGIN_EXTENDED_NOT_HANDLED;
+}
+
+
+static int
+check_replica_id_uniqueness(Replica *replica, RUV *supplier_ruv)
+{
+ ReplicaId local_rid = replica_get_rid(replica);
+ ReplicaId sup_rid = 0;
+ char *sup_purl = NULL;
+
+ if (ruv_get_first_id_and_purl(supplier_ruv, &sup_rid, &sup_purl) == RUV_SUCCESS) {
+ /* ReplicaID Uniqueness is checked only on Masters */
+ if ((replica_get_type(replica) == REPLICA_TYPE_UPDATABLE) &&
+ (sup_rid == local_rid)) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+
diff --git a/ldap/servers/plugins/replication/repl_globals.c b/ldap/servers/plugins/replication/repl_globals.c
new file mode 100644
index 00000000..bee5dc4a
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl_globals.c
@@ -0,0 +1,108 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#include "nspr.h"
+#include "repl.h"
+
+char *repl_plugin_name = REPL_PLUGIN_NAME;
+char *repl_plugin_name_cl = REPL_PLUGIN_NAME " - changelog program";
+
+/* String constants (no need to change these for I18N) */
+
+#define CHANGETYPE_ADD "add"
+#define CHANGETYPE_DELETE "delete"
+#define CHANGETYPE_MODIFY "modify"
+#define CHANGETYPE_MODRDN "modrdn"
+#define CHANGETYPE_MODDN "moddn"
+#define ATTR_CHANGENUMBER "changenumber"
+#define ATTR_TARGETDN "targetdn"
+#define ATTR_CHANGETYPE "changetype"
+#define ATTR_NEWRDN "newrdn"
+#define ATTR_DELETEOLDRDN "deleteoldrdn"
+#define ATTR_CHANGES "changes"
+#define ATTR_NEWSUPERIOR "newsuperior"
+#define ATTR_CHANGETIME "changetime"
+#define ATTR_DATAVERSION "dataVersion"
+#define ATTR_CSN "csn"
+#define TYPE_COPYINGFROM "copyingFrom"
+#define TYPE_COPIEDFROM "copiedFrom"
+#define FILTER_COPYINGFROM "copyingFrom=*"
+#define FILTER_COPIEDFROM "copiedFrom=*"
+#define FILTER_OBJECTCLASS "objectclass=*"
+
+
+char *changetype_add = CHANGETYPE_ADD;
+char *changetype_delete = CHANGETYPE_DELETE;
+char *changetype_modify = CHANGETYPE_MODIFY;
+char *changetype_modrdn = CHANGETYPE_MODRDN;
+char *changetype_moddn = CHANGETYPE_MODDN;
+char *attr_changenumber = ATTR_CHANGENUMBER;
+char *attr_targetdn = ATTR_TARGETDN;
+char *attr_changetype = ATTR_CHANGETYPE;
+char *attr_newrdn = ATTR_NEWRDN;
+char *attr_deleteoldrdn = ATTR_DELETEOLDRDN;
+char *attr_changes = ATTR_CHANGES;
+char *attr_newsuperior = ATTR_NEWSUPERIOR;
+char *attr_changetime = ATTR_CHANGETIME;
+char *attr_dataversion = ATTR_DATAVERSION;
+char *attr_csn = ATTR_CSN;
+char *type_copyingFrom = TYPE_COPYINGFROM;
+char *type_copiedFrom = TYPE_COPIEDFROM;
+char *filter_copyingFrom = FILTER_COPYINGFROM;
+char *filter_copiedFrom = FILTER_COPIEDFROM;
+char *filter_objectclass = FILTER_OBJECTCLASS;
+char *type_cn = "cn";
+char *type_objectclass = "objectclass";
+
+/* Names for replica attributes */
+const char *attr_replicaId = "nsDS5ReplicaId";
+const char *attr_replicaRoot = "nsDS5ReplicaRoot";
+const char *attr_replicaType = "nsDS5ReplicaType";
+const char *attr_replicaBindDn = "nsDS5ReplicaBindDn";
+const char *attr_state = "nsState";
+const char *attr_flags = "nsds5Flags";
+const char *attr_replicaName = "nsds5ReplicaName";
+const char *attr_replicaReferral = "nsds5ReplicaReferral";
+const char *type_ruvElement = "nsds50ruv";
+const char *type_replicaPurgeDelay = "nsds5ReplicaPurgeDelay";
+const char *type_replicaChangeCount = "nsds5ReplicaChangeCount";
+const char *type_replicaTombstonePurgeInterval = "nsds5ReplicaTombstonePurgeInterval";
+const char *type_replicaLegacyConsumer = "nsds5ReplicaLegacyConsumer";
+const char *type_ruvElementUpdatetime = "nsruvReplicaLastModified";
+
+/* Attribute names for replication agreement attributes */
+const char *type_nsds5ReplicaHost = "nsds5ReplicaHost";
+const char *type_nsds5ReplicaPort = "nsds5ReplicaPort";
+const char *type_nsds5TransportInfo = "nsds5ReplicaTransportInfo";
+const char *type_nsds5ReplicaBindDN = "nsds5ReplicaBindDN";
+const char *type_nsds5ReplicaCredentials = "nsds5ReplicaCredentials";
+const char *type_nsds5ReplicaBindMethod = "nsds5ReplicaBindMethod";
+const char *type_nsds5ReplicaRoot = "nsds5ReplicaRoot";
+const char *type_nsds5ReplicatedAttributeList = "nsds5ReplicatedAttributeList";
+const char *type_nsds5ReplicaUpdateSchedule = "nsds5ReplicaUpdateSchedule";
+const char *type_nsds5ReplicaInitialize = "nsds5BeginReplicaRefresh";
+const char *type_nsds5ReplicaTimeout = "nsds5ReplicaTimeout";
+const char *type_nsds5ReplicaBusyWaitTime = "nsds5ReplicaBusyWaitTime";
+const char *type_nsds5ReplicaSessionPauseTime = "nsds5ReplicaSessionPauseTime";
+
+/* To Allow Consumer Initialisation when adding an agreement - */
+const char *type_nsds5BeginReplicaRefresh = "nsds5BeginReplicaRefresh";
+
+static int repl_active_threads;
+
+int
+decrement_repl_active_threads()
+{
+ PR_AtomicIncrement(&repl_active_threads);
+ return repl_active_threads;
+}
+
+int
+increment_repl_active_threads()
+{
+ PR_AtomicDecrement(&repl_active_threads);
+ return repl_active_threads;
+}
diff --git a/ldap/servers/plugins/replication/repl_helper.c b/ldap/servers/plugins/replication/repl_helper.c
new file mode 100644
index 00000000..05616cf1
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl_helper.c
@@ -0,0 +1,85 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include "repl_helper.h"
+
+ReplGenericList *
+ReplGenericListNew(void)
+{
+ ReplGenericList *list=NULL;
+ if(NULL == (list = (ReplGenericList *)
+ slapi_ch_calloc(1,sizeof(ReplGenericList)))) {
+ return(NULL);
+ }
+ list->object = NULL;
+ list->next = NULL;
+ list->prev = NULL;
+ return(list);
+}
+
+void
+ReplGenericListAddObject(ReplGenericList *list,
+ void *newObject)
+{
+ if(list) {
+ ReplGenericList *new_struct = (ReplGenericList *)
+ slapi_ch_calloc(1, sizeof(ReplGenericList));
+
+ if (!new_struct)
+ return;
+ /* set back pointer of old first element */
+ if(list->next) {
+ list->next->prev = new_struct;
+ }
+
+ /* we might have a next but since we are the first we WONT have
+ a previous */
+ new_struct->object = newObject;
+ new_struct->next = list->next;
+ new_struct->prev = NULL;
+
+ /* the new element is the first one */
+ list->next = new_struct;
+
+ /* if this is the only element it is the end too */
+ if(NULL == list->prev)
+ list->prev = new_struct;
+
+ }
+ return;
+}
+
+ReplGenericList *
+ReplGenericListFindObject(ReplGenericList *list,
+ void *object)
+{
+ if(!list)
+ return(NULL);
+ list = list->next; /* the first list item never has data */
+
+ while (list) {
+ if(list->object == object)
+ return(list);
+ list = list->next;
+ }
+ return(NULL);
+}
+
+void
+ReplGenericListDestroy(ReplGenericList *list,
+ ReplGenericListObjectDestroyFn destroyFn)
+{
+ ReplGenericList *list_ptr;
+
+ while (list) {
+ list_ptr = list;
+ list = list->next;
+ if(destroyFn && list_ptr->object) {
+ (destroyFn)(list_ptr->object);
+ }
+ slapi_ch_free((void **)(&list_ptr));
+ }
+ return;
+}
diff --git a/ldap/servers/plugins/replication/repl_helper.h b/ldap/servers/plugins/replication/repl_helper.h
new file mode 100644
index 00000000..076710ce
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl_helper.h
@@ -0,0 +1,69 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/*
+ * repl_helper.h - Helper functions (should actually be repl_utils.h)
+ *
+ *
+ *
+ */
+
+#ifndef _REPL_HELPER_H
+#define _REPL_HELPER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "nspr.h"
+#include "slapi-plugin.h"
+
+/*
+ * shamelessly stolen from the xp library
+ *
+ */
+
+/*
+ Linked list manipulation routines
+
+ this is a very standard linked list structure
+ used by many many programmers all over the world
+
+ The lists have been modified to be doubly linked. The
+ first element in a list is always the header. The 'next'
+ pointer of the header is the first element in the list.
+ The 'prev' pointer of the header is the last element in
+ the list.
+
+ The 'prev' pointer of the first real element in the list
+ is NULL as is the 'next' pointer of the last real element
+ in the list
+
+ */
+
+
+typedef struct _repl_genericList {
+ void *object;
+ struct _repl_genericList *next;
+ struct _repl_genericList *prev;
+} ReplGenericList;
+
+typedef void *(ReplGenericListObjectDestroyFn)(void *obj);
+
+ReplGenericList *ReplGenericListNew(void);
+void ReplGenericListDestroy(ReplGenericList *list, ReplGenericListObjectDestroyFn destroyFn);
+
+void ReplGenericListAddObject(ReplGenericList *list,
+ void *newObject);
+ReplGenericList *ReplGenericListFindObject(ReplGenericList *list,
+ void *obj);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
diff --git a/ldap/servers/plugins/replication/repl_init.c b/ldap/servers/plugins/replication/repl_init.c
new file mode 100644
index 00000000..42da2074
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl_init.c
@@ -0,0 +1,312 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+/*
+ * Add an entry like the following to dse.ldif to enable this plugin:
+
+dn: cn=Legacy Replication Plugin,cn=plugins,cn=config
+objectclass: top
+objectclass: nsSlapdPlugin
+objectclass: extensibleObject
+cn: Legacy Replication Plugin
+nsslapd-pluginpath: /export2/servers/Hydra-supplier/lib/replication-plugin.so
+nsslapd-plugininitfunc: replication_legacy_plugin_init
+nsslapd-plugintype: object
+nsslapd-pluginenabled: on
+nsslapd-plugin-depends-on-type: database
+nsslapd-plugin-depends-on-named: Class of Service
+nsslapd-plugin-depends-on-named: Multi-Master Replication Plugin
+nsslapd-pluginid: replication-legacy
+nsslapd-pluginversion: 5.0b1
+nsslapd-pluginvendor: Netscape Communications
+nsslapd-plugindescription: Legacy Replication Plugin
+
+NOTE: This plugin depends on the Multi-Master Replication Plugin
+
+*/
+
+#include "slapi-plugin.h"
+#include "repl.h"
+#include "repl5.h"
+#include "repl_shared.h"
+#include "cl4.h" /* changelog interface */
+#include "dirver.h"
+#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */
+
+#ifdef _WIN32
+int *module_ldap_debug = 0;
+
+void plugin_init_debug_level(int *level_ptr)
+{
+ module_ldap_debug = level_ptr;
+}
+#endif
+
+/* ----------------------------- Legacy Replication Plugin */
+
+static Slapi_PluginDesc legacydesc = { "replication-legacy", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Legacy Replication Plugin" };
+static Slapi_PluginDesc legacypreopdesc = { "replication-legacy-preop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Legacy replication pre-operation plugin" };
+static Slapi_PluginDesc legacypostopdesc = { "replication-legacy-postop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Legacy replication post-operation plugin" };
+static Slapi_PluginDesc legacyinternalpreopdesc = { "replication-legacy-internalpreop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Legacy replication internal pre-operation plugin" };
+static Slapi_PluginDesc legacyinternalpostopdesc = { "replication-legacy-internalpostop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Legacy replication internal post-operation plugin" };
+static Slapi_PluginDesc legacybepostopdesc = { "replication-legacy-bepostop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Legacy replication bepost-operation plugin" };
+static Slapi_PluginDesc legacyentrydesc = { "replication-legacy-entry", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Legacy replication entry plugin" };
+
+static int legacy_stopped; /* A flag which is set when all the plugin threads are to stop */
+
+
+/* Initialize preoperation plugin points */
+int
+legacy_preop_init( Slapi_PBlock *pb )
+{
+ int rc= 0; /* OK */
+
+ if( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&legacypreopdesc ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_BIND_FN, (void *) legacy_preop_bind ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_ADD_FN, (void *) legacy_preop_add ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_DELETE_FN, (void *) legacy_preop_delete ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_MODIFY_FN, (void *) legacy_preop_modify ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_MODRDN_FN, (void *) legacy_preop_modrdn ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_SEARCH_FN, (void *) legacy_preop_search ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_COMPARE_FN, (void *) legacy_preop_compare ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_ENTRY_FN, (void *) legacy_pre_entry ))
+ {
+ slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "legacy_preop_init failed\n" );
+ rc= -1;
+ }
+ return rc;
+}
+
+
+
+/* Initialize postoperation plugin points */
+static int
+legacy_postop_init( Slapi_PBlock *pb )
+{
+ int rc= 0; /* OK */
+
+ if( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&legacypostopdesc ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_POST_ADD_FN, (void *) legacy_postop_add ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_POST_DELETE_FN, (void *) legacy_postop_delete ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_POST_MODIFY_FN, (void *) legacy_postop_modify ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_POST_MODRDN_FN, (void *) legacy_postop_modrdn ) != 0 )
+ {
+ slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "legacy_postop_init failed\n" );
+ rc= -1;
+ }
+
+ return rc;
+}
+
+
+
+/* Initialize internal preoperation plugin points (called for internal operations) */
+static int
+legacy_internalpreop_init( Slapi_PBlock *pb )
+{
+ int rc= 0; /* OK */
+
+ if( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&legacyinternalpreopdesc ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_PRE_ADD_FN, (void *) legacy_preop_add ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_PRE_DELETE_FN, (void *) legacy_preop_delete ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_PRE_MODIFY_FN, (void *) legacy_preop_modify ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_PRE_MODRDN_FN, (void *) legacy_preop_modrdn ) != 0 )
+ {
+ slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "legacy_internalpreop_init failed\n" );
+ rc= -1;
+ }
+ return rc;
+}
+
+
+
+/* Initialize internal postoperation plugin points (called for internal operations) */
+static int
+legacy_internalpostop_init( Slapi_PBlock *pb )
+{
+ int rc= 0; /* OK */
+
+ if( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&legacyinternalpostopdesc ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_POST_ADD_FN, (void *) legacy_postop_add ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_POST_DELETE_FN, (void *) legacy_postop_delete ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_POST_MODIFY_FN, (void *) legacy_postop_modify ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_POST_MODRDN_FN, (void *) legacy_postop_modrdn ) != 0 )
+ {
+ slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "legacy_internalpostop_init failed\n" );
+ rc= -1;
+ }
+
+ return rc;
+}
+
+
+
+/* Initialize the entry plugin point for the legacy replication plugin */
+static int
+legacy_entry_init( Slapi_PBlock *pb )
+{
+ int rc= 0; /* OK */
+
+ /* Set up the fn pointers for the preop and postop operations we're interested in */
+ if( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&legacyentrydesc ) != 0 )
+ {
+ slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "legacy_entry_init failed\n" );
+ rc= -1;
+ }
+ return rc;
+}
+
+
+
+
+/*
+ * Create the entry at the top of the replication configuration subtree.
+ */
+static int
+create_config_top()
+{
+ const char *dn = REPL_CONFIG_TOP;
+ char *entry_string = slapi_ch_strdup("dn: cn=replication,cn=config\nobjectclass: top\nobjectclass: extensibleobject\ncn: replication\n");
+ Slapi_PBlock *pb = slapi_pblock_new();
+ Slapi_Entry *e = slapi_str2entry(entry_string, 0);
+ int return_value;
+
+ slapi_add_entry_internal_set_pb(pb, e, NULL, /* controls */
+ repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION), 0 /* flags */);
+ slapi_add_internal_pb(pb);
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &return_value);
+ slapi_pblock_destroy(pb);
+ slapi_ch_free((void **)&entry_string);
+ return return_value;
+}
+
+
+/* Start the legacy replication plugin */
+static int
+legacy_start( Slapi_PBlock *pb )
+{
+ static int legacy_started = 0;
+ int rc= 0; /* OK */
+
+ if (!legacy_started)
+ {
+ int ctrc;
+
+ /* Initialise support for cn=monitor */
+ repl_monitor_init();
+
+ /* Initialise support for "" (the rootdse) */
+ /* repl_rootdse_init(); */
+
+ /* Decode the command line args to see if we're dumping to LDIF */
+ {
+ int argc;
+ char **argv;
+ slapi_pblock_get( pb, SLAPI_ARGC, &argc);
+ slapi_pblock_get( pb, SLAPI_ARGV, &argv);
+ repl_entry_init(argc,argv);
+ }
+
+ /* Create the entry at the top of the config area, if it doesn't exist */
+ /* XXXggood this should be in the 5.0 plugin! */
+ ctrc = create_config_top();
+ if (ctrc != LDAP_SUCCESS && ctrc != LDAP_ALREADY_EXISTS)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Warning: unable to "
+ "create configuration entry %s: %s\n", REPL_CONFIG_TOP,
+ ldap_err2string(ctrc));
+ }
+ (void)legacy_consumer_config_init();
+
+ /* register to be notified when backend state changes */
+ slapi_register_backend_state_change((void *)legacy_consumer_be_state_change,
+ legacy_consumer_be_state_change);
+
+ legacy_started = 1;
+ legacy_stopped = 0;
+ }
+ return rc;
+}
+
+
+/* Post-start function for the legacy replication plugin */
+static int
+legacy_poststart( Slapi_PBlock *pb )
+{
+ int rc = 0; /* OK */
+ return rc;
+}
+
+
+/* Stop the legacy replication plugin */
+static int
+legacy_stop( Slapi_PBlock *pb )
+{
+ int rc= 0; /* OK */
+
+ if (!legacy_stopped)
+ {
+ /*csnShutdown();*/
+ legacy_stopped = 1;
+ }
+
+ /* unregister backend state change notification */
+ slapi_unregister_backend_state_change((void *)legacy_consumer_be_state_change);
+
+ return rc;
+}
+
+
+/* Initialize the legacy replication plugin */
+int
+replication_legacy_plugin_init(Slapi_PBlock *pb)
+{
+ static int legacy_initialised= 0;
+ int rc= 0; /* OK */
+ void *identity = NULL;
+
+ slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &identity);
+ PR_ASSERT (identity);
+ repl_set_plugin_identity (PLUGIN_LEGACY_REPLICATION, identity);
+
+ if(config_is_slapd_lite())
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name,
+ "replication plugin not approved for restricted"
+ " mode Directory Server.\n" );
+ rc= -1;
+ }
+ if(rc==0 && !legacy_initialised)
+ {
+ rc= slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 );
+ rc= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&legacydesc );
+ rc= slapi_pblock_set( pb, SLAPI_PLUGIN_START_FN, (void *) legacy_start );
+ rc= slapi_pblock_set( pb, SLAPI_PLUGIN_CLOSE_FN, (void *) legacy_stop );
+ rc= slapi_pblock_set( pb, SLAPI_PLUGIN_POSTSTART_FN, (void *) legacy_poststart );
+
+ /* Register the plugin interfaces we implement */
+ rc= slapi_register_plugin("preoperation", 1 /* Enabled */, "legacy_preop_init", legacy_preop_init, "Legacy replication preoperation plugin", NULL, identity);
+ rc= slapi_register_plugin("postoperation", 1 /* Enabled */, "legacy_postop_init", legacy_postop_init, "Legacy replication postoperation plugin", NULL, identity);
+ rc= slapi_register_plugin("internalpreoperation", 1 /* Enabled */, "legacy_internalpreop_init", legacy_internalpreop_init, "Legacy replication internal preoperation plugin", NULL, identity);
+ rc= slapi_register_plugin("internalpostoperation", 1 /* Enabled */, "legacy_internalpostop_init", legacy_internalpostop_init, "Legacy replication internal postoperation plugin", NULL, identity);
+ rc= slapi_register_plugin("entry", 1 /* Enabled */, "legacy_entry_init", legacy_entry_init, "Legacy replication entry plugin", NULL, identity);
+
+ legacy_initialised= 1;
+ }
+ return rc;
+}
+
+
+int
+get_legacy_stop()
+{
+ return legacy_stopped;
+}
diff --git a/ldap/servers/plugins/replication/repl_modify.c b/ldap/servers/plugins/replication/repl_modify.c
new file mode 100644
index 00000000..26753cc0
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl_modify.c
@@ -0,0 +1,29 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#include "slapi-plugin.h"
+#include "repl.h"
+
+/* The modify operation plugin functions for the legacy replication plugin */
+
+int
+legacy_preop_modify( Slapi_PBlock *pb )
+{
+ return legacy_preop( pb, "legacy_preop_modify", OP_MODIFY );
+}
+
+int
+legacy_bepreop_modify( Slapi_PBlock *pb )
+{
+ int rc= 0; /* OK */
+ return rc;
+}
+
+int
+legacy_postop_modify( Slapi_PBlock *pb )
+{
+ return legacy_postop( pb, "legacy_postop_modify", OP_MODIFY );
+}
diff --git a/ldap/servers/plugins/replication/repl_modrdn.c b/ldap/servers/plugins/replication/repl_modrdn.c
new file mode 100644
index 00000000..653aff0a
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl_modrdn.c
@@ -0,0 +1,28 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#include "slapi-plugin.h"
+#include "repl.h"
+
+/* The modrdn plugin points for the legacy replication plugin */
+
+int
+legacy_preop_modrdn( Slapi_PBlock *pb )
+{
+ return legacy_preop(pb, "legacy_preop_modrdn", OP_MODDN);
+}
+
+int
+legacy_bepreop_modrdn( Slapi_PBlock *pb )
+{
+ return 0; /* OK */
+}
+
+int
+legacy_postop_modrdn( Slapi_PBlock *pb )
+{
+ return legacy_postop(pb, "legacy_postop_modrdn", OP_MODDN);
+}
diff --git a/ldap/servers/plugins/replication/repl_monitor.c b/ldap/servers/plugins/replication/repl_monitor.c
new file mode 100644
index 00000000..ec52d611
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl_monitor.c
@@ -0,0 +1,58 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#include <string.h>
+
+#include "repl.h"
+#include "slapi-plugin.h"
+
+/* Forward Declartions */
+static int repl_monitor_search (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg);
+
+int
+repl_monitor_init()
+{
+ /* The FE DSE *must* be initialised before we get here */
+ int return_value= LDAP_SUCCESS;
+ static int initialized = 0;
+
+ if (!initialized)
+ {
+ /* ONREPL - this is commented until we implement 4.0 style changelog
+ slapi_config_register_callback(SLAPI_OPERATION_SEARCH,DSE_FLAG_PREOP,"cn=monitor",LDAP_SCOPE_BASE,"(objectclass=*)",repl_monitor_search,NULL); */
+ initialized = 1;
+ }
+
+ return return_value;
+}
+
+static int
+repl_monitor_search(Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg)
+{
+ const char *sdv = get_server_dataversion();
+ if ( sdv != NULL )
+ {
+ int port;
+ char buf[BUFSIZ];
+ struct berval val;
+ struct berval *vals[2];
+ vals[0] = &val;
+ vals[1] = NULL;
+ port= config_get_port();
+ if(port==0)
+ {
+ port= config_get_secureport();
+ }
+ /* ONREPL - how do we publish changenumbers now with multiple changelogs?
+ sprintf( buf, "%s:%lu %s% lu", get_localhost_DNS(), port, sdv, ldapi_get_last_changenumber());
+ */
+ val.bv_val = buf;
+ val.bv_len = strlen( buf );
+ slapi_entry_attr_replace( e, attr_dataversion, vals );
+ }
+ return SLAPI_DSE_CALLBACK_OK;
+}
+
diff --git a/ldap/servers/plugins/replication/repl_objset.c b/ldap/servers/plugins/replication/repl_objset.c
new file mode 100644
index 00000000..f0a68097
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl_objset.c
@@ -0,0 +1,524 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+/* repl_objset.c */
+/*
+ * Support for lifetime management of sets of objects.
+ * Objects are refcounted. NOTE: this API is deprecated.
+ * Use the object/objset API provided by libslapd.
+ */
+
+#include "slapi-plugin.h"
+#include "slapi-private.h"
+#include "repl_objset.h"
+#include <prlock.h>
+
+#define REPL_OBJSET_OBJ_FLAG_DELETED 0x1
+
+
+typedef struct repl_objset_object
+{
+ void *data; /* pointer to actual node data */
+ char *key; /* key for this object. null-terminated string */
+ int refcnt; /* reference count for this object */
+ unsigned long flags; /* state of this object */
+} Repl_Objset_object;
+
+typedef struct repl_objset
+{
+ LList *objects;
+ FNFree destructor; /* destructor for objects - provided by caller */
+ PRLock *lock;
+} repl_objset;
+
+
+/* Forward declarations */
+static void removeObjectNolock(Repl_Objset *o, Repl_Objset_object *co);
+static Repl_Objset_object *removeCurrentObjectAndGetNextNolock (Repl_Objset *o,
+ Repl_Objset_object *co, void *iterator);
+
+/*
+ * Create a new set.
+ *
+ * Arguments:
+ * destructor: a function to be called when an object is to be destroyed
+ *
+ * Returns:
+ * A pointer to the object set, or NULL if an error occured.
+ */
+Repl_Objset *
+repl_objset_new(FNFree destructor)
+{
+ Repl_Objset *p;
+
+ p = (Repl_Objset *)slapi_ch_malloc(sizeof(Repl_Objset));
+ p->lock = PR_NewLock();
+ if (NULL == p->lock)
+ {
+ free(p); p = NULL;
+ }
+ p->objects = llistNew();
+ p->destructor = destructor;
+ return p;
+}
+
+
+/*
+ * Destroy a Repl_Objset.
+ * Arguments:
+ * o: the object set to be destroyed
+ * maxwait: the maximum time to wait for all object refcnts to
+ * go to zero.
+ * panic_fn: a function to be called if, after waiting "maxwait"
+ * seconds, not all object refcnts are zero.
+ * The caller must ensure that no one else holds references to the
+ * set or any objects it contains.
+ */
+void
+repl_objset_destroy(Repl_Objset **o, time_t maxwait, FNFree panic_fn)
+{
+ Repl_Objset_object *co = NULL;
+ time_t now, stop_time;
+ int really_gone;
+ int loopcount;
+ void *cookie;
+
+ PR_ASSERT(NULL != o);
+ PR_ASSERT(NULL != *o);
+
+ time(&now);
+ stop_time = now + maxwait;
+
+ /*
+ * Loop over the objects until they all are actually gone,
+ * or until maxwait seconds have passed.
+ */
+ really_gone = 0;
+ loopcount = 0;
+
+ while (now < stop_time)
+ {
+ void *cookie;
+
+ PR_Lock((*o)->lock);
+
+ if ((co = llistGetFirst((*o)->objects, &cookie)) == NULL)
+ {
+ really_gone = 1;
+ PR_Unlock((*o)->lock);
+ break;
+ }
+ while (NULL != co)
+ {
+ /* Set the deleted flag so the object isn't returned by iterator */
+ co->flags |= REPL_OBJSET_OBJ_FLAG_DELETED;
+ if (0 == co->refcnt)
+ {
+ /* Remove the object */
+ co = removeCurrentObjectAndGetNextNolock ((*o), co, cookie);
+
+ }
+ else
+ co = llistGetNext((*o)->objects, &cookie);
+ }
+ PR_Unlock((*o)->lock);
+ time(&now);
+ if (loopcount > 0)
+ {
+ DS_Sleep(PR_TicksPerSecond());
+ }
+ loopcount++;
+ }
+
+ if (!really_gone)
+ {
+ if (NULL != panic_fn)
+ {
+ /*
+ * Call the "aargh, this thing won't go away" panic
+ * function for each remaining object.
+ */
+ PR_Lock((*o)->lock);
+ if ((co = llistGetFirst((*o)->objects, &cookie)) == NULL)
+ {
+ panic_fn(co->data);
+ while (NULL != co)
+ {
+ panic_fn(co->data);
+ co = llistGetNext((*o)->objects, &cookie);
+ }
+ }
+ PR_Unlock((*o)->lock);
+ }
+ }
+ else
+ {
+ /* Free the linked list */
+ llistDestroy(&(*o)->objects, (*o)->destructor);
+ PR_DestroyLock((*o)->lock);
+ free(*o); *o = NULL;
+ }
+}
+
+
+
+/*
+ * Add an object to an object set.
+ *
+ * Arguments:
+ * o: The object set to which the object is to be added.
+ * name: a null-terminated string that names the object. Must
+ * be unique.
+ * obj: pointer to the object to be added.
+ *
+ * Return codes:
+ * REPL_OBJSET_SUCCESS: the item was added to the object set
+ * REPL_OBJSET_DUPLICATE_KEY: an item with the same key is already
+ * in the object set.
+ * REPL_OBJSET_INTERNAL_ERROR: something bad happened.
+ */
+int
+repl_objset_add(Repl_Objset *o, const char *name, void *obj)
+{
+ Repl_Objset_object *co = NULL;
+ Repl_Objset_object *tmp = NULL;
+ int rc = REPL_OBJSET_SUCCESS;
+
+ PR_ASSERT(NULL != o);
+ PR_ASSERT(NULL != name);
+ PR_ASSERT(NULL != obj);
+
+ PR_Lock(o->lock);
+ tmp = llistGet(o->objects, name);
+ if (NULL != tmp)
+ {
+ rc = REPL_OBJSET_DUPLICATE_KEY;
+ goto loser;
+ }
+ co = (Repl_Objset_object *)slapi_ch_malloc(sizeof(Repl_Objset_object));
+ co->data = obj;
+ co->key = slapi_ch_strdup(name);
+ co->refcnt = 0;
+ co->flags = 0UL;
+ if (llistInsertHead(o->objects, name, co) != 0)
+ {
+ rc = REPL_OBJSET_INTERNAL_ERROR;
+ goto loser;
+ }
+ PR_Unlock(o->lock);
+ return rc;
+
+loser:
+ PR_Unlock(o->lock);
+ if (NULL != co)
+ {
+ if (NULL != co->key)
+ {
+ slapi_ch_free((void **)&co->key);
+ }
+ slapi_ch_free((void **)&co);
+ }
+ return rc;
+}
+
+
+/* Must be called with the repl_objset locked */
+static void
+removeObjectNolock(Repl_Objset *o, Repl_Objset_object *co)
+{
+ /* Remove from list */
+ llistRemove(o->objects, co->key);
+ /* Destroy the object */
+ o->destructor(&(co->data));
+ free(co->key);
+ /* Deallocate the container */
+ free(co);
+}
+
+static Repl_Objset_object *
+removeCurrentObjectAndGetNextNolock (Repl_Objset *o, Repl_Objset_object *co, void *iterator)
+{
+ Repl_Objset_object *ro;
+
+ PR_ASSERT (o);
+ PR_ASSERT (co);
+ PR_ASSERT (iterator);
+
+ ro = llistRemoveCurrentAndGetNext (o->objects, &iterator);
+
+ o->destructor(&(co->data));
+ free(co->key);
+ /* Deallocate the container */
+ free(co);
+
+ return ro;
+}
+
+/* Must be called with the repl_objset locked */
+static void
+acquireNoLock(Repl_Objset_object *co)
+{
+ co->refcnt++;
+}
+
+
+/* Must be called with the repl_objset locked */
+static void
+releaseNoLock(Repl_Objset *o, Repl_Objset_object *co)
+{
+ PR_ASSERT(co->refcnt >= 1);
+ if (--co->refcnt == 0)
+ {
+ if (co->flags & REPL_OBJSET_OBJ_FLAG_DELETED)
+ {
+ /* Remove the object */
+ removeObjectNolock(o, co);
+ }
+ }
+}
+
+/*
+ * Retrieve an object from the object set. If an object with
+ * the given key is found, its reference count is incremented,
+ * a pointer to the object is returned, and a handle to use
+ * to refer to the object is returned.
+ *
+ * Arguments:
+ * o: The object set to be searched.
+ * key: key of the object to be retrieved
+ * obj: pointer to void * that will be set to point to the
+ * object, if found.
+ * handle: pointer to void * that will be set to point to a
+ * handle, used to refer to the object, if found.
+ *
+ * Returns:
+ * REPL_OBJSET_SUCCESS: an item was found.
+ * REPL_OBJSET_KEY_NOT_FOUND: no item with the given key was found.
+ */
+int
+repl_objset_acquire(Repl_Objset *o, const char *key, void **obj, void **handle)
+{
+ Repl_Objset_object *co = NULL;
+ int rc = REPL_OBJSET_KEY_NOT_FOUND;
+
+ PR_ASSERT(NULL != o);
+ PR_ASSERT(NULL != key);
+ PR_ASSERT(NULL != obj);
+ PR_ASSERT(NULL != handle);
+
+ PR_Lock(o->lock);
+ co = llistGet(o->objects, key);
+ if (NULL != co && !(co->flags & REPL_OBJSET_OBJ_FLAG_DELETED))
+ {
+ acquireNoLock(co);
+ *obj = (void *)co->data;
+ *handle = (void *)co;
+ rc = REPL_OBJSET_SUCCESS;
+ }
+ PR_Unlock(o->lock);
+ return rc;
+}
+
+
+/*
+ * Return an object to the object set.
+ *
+ * Arguments:
+ * o: The object set containing the objct
+ * handle: reference to the object.
+ *
+ */
+void
+repl_objset_release(Repl_Objset *o, void *handle)
+{
+ Repl_Objset_object *co;
+
+ PR_ASSERT(NULL != o);
+ PR_ASSERT(NULL != handle);
+
+ co = (Repl_Objset_object *)handle;
+ PR_Lock(o->lock);
+ releaseNoLock(o, co);
+ PR_Unlock(o->lock);
+}
+
+
+
+/*
+ * Delete an object from the object set
+ *
+ * Arguments:
+ * o: The object set containing the object.
+ * handle: reference to the object.
+ */
+void
+repl_objset_delete(Repl_Objset *o, void *handle)
+{
+ Repl_Objset_object *co = (Repl_Objset_object *)handle;
+
+ PR_ASSERT(NULL != o);
+ PR_ASSERT(NULL != co);
+
+ PR_Lock(o->lock);
+ if (co->refcnt == 0)
+ {
+ removeObjectNolock(o, co);
+ }
+ else
+ {
+ /* Set deleted flag, clean up later */
+ co->flags |= REPL_OBJSET_OBJ_FLAG_DELETED;
+ }
+ PR_Unlock(o->lock);
+}
+
+
+typedef struct _iterator
+{
+ Repl_Objset *o; /* set for which iterator was created */
+ void *cookie; /* for linked list */
+ Repl_Objset_object *co; /* our wrapper */
+} iterator;
+
+/*
+ * Get the first object in an object set.
+ * Used when enumerating all of the objects in a set.
+ * Arguments:
+ * o: The object set being enumerated
+ * itcontext: an iteration context, to be passed back to subsequent calls
+ * to repl_objset_next_object.
+ * handle: a pointer to pointer to void. This will be filled in with
+ * a reference to the object's enclosing object.
+ * Returns:
+ * A pointer to the next object in the set, or NULL if there are no
+ * objects in the set.
+ *
+ */
+void *
+repl_objset_first_object(Repl_Objset *o, void **itcontext, void **handle)
+{
+ Repl_Objset_object *co = NULL;
+ void *cookie;
+ void *retptr = NULL;
+ iterator *it;
+
+ PR_ASSERT(NULL != o);
+ PR_ASSERT(NULL != itcontext);
+
+ *itcontext = NULL;
+
+ if (NULL == o->objects) {
+ return(NULL);
+ }
+
+ /* Find the first non-deleted object */
+ PR_Lock(o->lock);
+ co = llistGetFirst(o->objects, &cookie);
+ while (NULL != co && (co->flags & REPL_OBJSET_OBJ_FLAG_DELETED))
+ {
+ co = llistGetNext(o->objects, &cookie);
+ }
+
+ if (NULL != co)
+ {
+ /* Increment refcnt until item given back to us */
+ acquireNoLock(co);
+
+ /* Save away context */
+ it = (iterator *)slapi_ch_malloc(sizeof(iterator));
+ *itcontext = it;
+ it->o = o;
+ it->cookie = cookie;
+ it->co = co;
+ retptr = co->data;
+ }
+
+ PR_Unlock(o->lock);
+ if (NULL != handle)
+ {
+ *handle = co;
+ }
+
+ return retptr;
+}
+
+
+
+/*
+ * Get the next object in the set.
+ * Arguments:
+ * o: The object set being enumerated
+ * itcontext: an iteration context, to be passed back to subsequent calls
+ * to repl_objset_next_object.
+ * handle: a pointer to pointer to void. This will be filled in with
+ * a reference to the object's enclosing object.
+ *
+ * Returns:
+ * A pointer to the next object in the set, or NULL if there are no more
+ * objects.
+ */
+void *
+repl_objset_next_object(Repl_Objset *o, void *itcontext, void **handle)
+{
+ Repl_Objset_object *co = NULL;
+ Repl_Objset_object *tmp_co;
+ void *retptr = NULL;
+ iterator *it = (iterator *)itcontext;
+
+ PR_ASSERT(NULL != o);
+ PR_ASSERT(NULL != it);
+ PR_ASSERT(NULL != it->co);
+
+ PR_Lock(o->lock);
+ tmp_co = it->co;
+
+ /* Find the next non-deleted object */
+ while ((co = llistGetNext(o->objects, &it->cookie)) != NULL &&
+ !(co->flags & REPL_OBJSET_OBJ_FLAG_DELETED));
+
+ if (NULL != co)
+ {
+ acquireNoLock(co);
+ it->co = co;
+ retptr = co->data;
+ }
+ else
+ {
+ /*
+ * No more non-deleted objects - erase context (freeing
+ * it is responsibility of caller.
+ */
+ it->cookie = NULL;
+ it->co = NULL;
+ }
+ releaseNoLock(o, tmp_co);
+ PR_Unlock(o->lock);
+ if (NULL != handle)
+ {
+ *handle = co;
+ }
+ return retptr;
+}
+
+
+
+/*
+ * Destroy an itcontext iterator
+ */
+void
+repl_objset_iterator_destroy(void **itcontext)
+{
+ if (NULL != itcontext && NULL != *itcontext)
+ {
+ /* check if we did not iterate through the entire list
+ and need to release last accessed element */
+ iterator *it = *(iterator**)itcontext;
+ if (it->co)
+ repl_objset_release (it->o, it->co);
+
+ slapi_ch_free((void **)itcontext);
+ }
+}
diff --git a/ldap/servers/plugins/replication/repl_objset.h b/ldap/servers/plugins/replication/repl_objset.h
new file mode 100644
index 00000000..72af5109
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl_objset.h
@@ -0,0 +1,37 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/*
+ */
+
+/* repl_objset.h */
+ /*
+ * Support for lifetime management of sets of objects.
+ * Objects are refcounted. NOTE: This API should go away
+ * in favor of the objset API provided by libslapd.
+ */
+#ifndef _REPL_OBJSET_H
+#define __REPL_OBJSET_H
+
+#include "llist.h"
+
+#define REPL_OBJSET_SUCCESS 0
+#define REPL_OBJSET_DUPLICATE_KEY 1
+#define REPL_OBJSET_INTERNAL_ERROR 2
+#define REPL_OBJSET_KEY_NOT_FOUND 3
+
+typedef struct repl_objset Repl_Objset;
+
+Repl_Objset *repl_objset_new(FNFree destructor);
+void repl_objset_destroy(Repl_Objset **o, time_t maxwait, FNFree panic_fn);
+int repl_objset_add(Repl_Objset *o, const char *name, void *obj);
+int repl_objset_acquire(Repl_Objset *o, const char *key, void **obj, void **handle);
+void repl_objset_release(Repl_Objset *o, void *handle);
+void repl_objset_delete(Repl_Objset *o, void *handle);
+void *repl_objset_next_object(Repl_Objset *o, void *cookie, void **handle);
+void *repl_objset_first_object(Repl_Objset *o, void **cookie, void **handle);
+void repl_objset_iterator_destroy(void **itcontext);
+
+#endif /* _REPL_OBJSET_H */
diff --git a/ldap/servers/plugins/replication/repl_opext.c b/ldap/servers/plugins/replication/repl_opext.c
new file mode 100644
index 00000000..d9c8d1ed
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl_opext.c
@@ -0,0 +1,97 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* supplier_operation_extension.c - replication extension to the Operation object
+ */
+
+
+#include "repl.h"
+#include "repl5.h"
+
+/* ***** Supplier side ***** */
+
+/* JCMREPL -> PINAKIxxx The interface to the referral stuff is not correct */
+void ref_array_dup_free(void *the_copy); /* JCMREPL - should be #included */
+
+/* supplier operation extension constructor */
+void* supplier_operation_extension_constructor (void *object, void *parent)
+{
+ supplier_operation_extension *ext = (supplier_operation_extension*) slapi_ch_calloc (1, sizeof (supplier_operation_extension));
+ if (ext == NULL)
+ {
+ slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "unable to create replication supplier operation extension - out of memory\n" );
+ }
+ else
+ {
+ ext->prevent_recursive_call= 0;
+ }
+ return ext;
+}
+
+/* supplier operation extension destructor */
+void supplier_operation_extension_destructor (void *ext,void *object, void *parent)
+{
+ if (ext)
+ {
+ supplier_operation_extension *supext = (supplier_operation_extension *)ext;
+ if (supext->operation_parameters)
+ operation_parameters_free (&(supext->operation_parameters));
+ if (supext->repl_gen)
+ slapi_ch_free ((void**)&supext->repl_gen);
+ slapi_ch_free((void **)&ext);
+ }
+}
+
+/* ***** Consumer side ***** */
+
+/* consumer operation extension constructor */
+void* consumer_operation_extension_constructor (void *object, void *parent)
+{
+ consumer_operation_extension *ext = (consumer_operation_extension*) slapi_ch_calloc (1, sizeof (consumer_operation_extension));
+ if (ext == NULL)
+ {
+ slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "unable to create replication consumer operation extension - out of memory\n" );
+ }
+ if(object!=NULL && parent!=NULL)
+ {
+ consumer_connection_extension *connext;
+ connext = (consumer_connection_extension *)repl_con_get_ext(REPL_CON_EXT_CONN, parent);
+ if(NULL != connext)
+ {
+ /* We copy the Connection Replicated Session flag to the Replicated Operation flag */
+ if (connext->isreplicationsession)
+ {
+ operation_set_flag((Slapi_Operation *)object,OP_FLAG_REPLICATED);
+ }
+ /* We set the Replication DN flag if session bound as replication dn */
+ if (connext->is_legacy_replication_dn)
+ {
+ operation_set_flag((Slapi_Operation *)object, OP_FLAG_LEGACY_REPLICATION_DN);
+ }
+ }
+ }
+ else
+ {
+ /* (parent==NULL) for internal operations */
+ PR_ASSERT(object!=NULL);
+ }
+
+ return ext;
+}
+
+/* consumer operation extension destructor */
+void consumer_operation_extension_destructor (void *ext,void *object, void *parent)
+{
+ if (NULL != ext)
+ {
+ consumer_operation_extension *opext = (consumer_operation_extension *)ext;
+ if (NULL != opext->search_referrals)
+ {
+ ref_array_dup_free(opext->search_referrals); /* JCMREPL - undefined */
+ opext->search_referrals = NULL;
+ }
+ slapi_ch_free((void **)&ext);
+ }
+}
diff --git a/ldap/servers/plugins/replication/repl_ops.c b/ldap/servers/plugins/replication/repl_ops.c
new file mode 100644
index 00000000..e1e51355
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl_ops.c
@@ -0,0 +1,180 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#include "slapi-plugin.h"
+#include "repl.h"
+#include "repl5.h"
+
+int
+legacy_postop( Slapi_PBlock *pb, const char *caller, int operation_type)
+{
+ int rc = 0;
+ Object *r_obj;
+ Replica *r;
+
+ r_obj = replica_get_replica_for_op (pb);
+ if (r_obj == NULL) /* there is no replica configured for this operations */
+ return 0;
+ else
+ {
+ /* check if this replica is 4.0 consumer */
+ r = (Replica*)object_get_data (r_obj);
+ PR_ASSERT (r);
+
+ /* this replica is not a 4.0 consumer - so we don't need to do any processing */
+ if (!replica_is_legacy_consumer (r))
+ {
+ object_release (r_obj);
+ return 0;
+ }
+
+ object_release (r_obj);
+ }
+
+ slapi_pblock_get(pb, SLAPI_PLUGIN_OPRETURN, &rc);
+ if (0 == rc)
+ {
+ if (OP_ADD == operation_type || OP_MODIFY == operation_type)
+ {
+ void *op;
+ consumer_operation_extension *opext = NULL;
+
+ /* Optimise out traversal of mods/entry if no cop{ied|ying}From present */
+ slapi_pblock_get(pb, SLAPI_OPERATION, &op);
+ opext = (consumer_operation_extension*) repl_con_get_ext (REPL_CON_EXT_OP, op);
+ if (NULL != opext && opext->has_cf)
+ {
+ process_legacy_cf( pb );
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+
+static char *not_replicationdn_errmsg =
+ "An operation was submitted that contained copiedFrom or "
+ "copyingFrom attributes, but the connection was not bound "
+ "as the replicationdn.";
+
+int
+legacy_preop(Slapi_PBlock *pb, const char *caller, int operation_type)
+{
+ int rc = 0;
+ Slapi_Operation *operation = NULL;
+ consumer_operation_extension *opext = NULL;
+ int has_cf = 0;
+ Object *r_obj;
+ Replica *r;
+ int is_legacy_op = 0;
+
+ slapi_pblock_get( pb, SLAPI_OPERATION, &operation );
+ is_legacy_op = operation_is_flag_set(operation,OP_FLAG_LEGACY_REPLICATION_DN);
+ r_obj = replica_get_replica_for_op (pb);
+
+ if (r_obj == NULL) { /* there is no replica configured for this operations */
+ if (is_legacy_op){
+ /* This is a legacy replication operation but there are NO replica defined
+ Just refuse it */
+ slapi_send_ldap_result(pb, LDAP_UNWILLING_TO_PERFORM, NULL,
+ "Replication operation refused because the consumer is not defined as a replica", 0, NULL);
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "Incoming replication operation was refused because "
+ "there's no replica defined for this operation\n");
+ return -1;
+ }
+ else {
+ return 0;
+ }
+ }
+ else
+ {
+ /* check if this replica is 4.0 consumer */
+ r = (Replica*)object_get_data (r_obj);
+ PR_ASSERT (r);
+
+ if (!replica_is_legacy_consumer (r))
+ {
+ object_release (r_obj);
+ if (is_legacy_op) {
+ /* This is a legacy replication operation
+ but the replica is doesn't accept from legacy
+ Just refuse it */
+ slapi_send_ldap_result(pb, LDAP_UNWILLING_TO_PERFORM, NULL,
+ "Replication operation refused because "
+ "the consumer is not defined as a legacy replica", 0, NULL);
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "Incoming replication operation was refused because "
+ "there's no legacy replica defined for this operation\n");
+ return -1;
+ } else {
+ return 0;
+ }
+ }
+
+ object_release (r_obj);
+ }
+
+ opext = (consumer_operation_extension*) repl_con_get_ext (REPL_CON_EXT_OP, operation);
+
+ switch (operation_type) {
+ case OP_ADD:
+ {
+ Slapi_Entry *e = NULL;
+ Slapi_Attr *attr;
+ /*
+ * Check if the entry being added has copiedFrom/copyingFrom
+ * attributes.
+ */
+ slapi_pblock_get(pb, SLAPI_ADD_ENTRY, &e);
+ if (NULL != e)
+ {
+ if (slapi_entry_attr_find(e, type_copiedFrom, &attr) == 0)
+ {
+ has_cf = 1;
+ }
+ else
+ if (slapi_entry_attr_find(e, type_copyingFrom, &attr) == 0)
+ {
+ has_cf = 1;
+ }
+ }
+ /* JCMREPL - If this is a replicated operation then the baggage control also contains the Unique Identifier of the superior entry. */
+ }
+ break;
+ case OP_MODIFY:
+ {
+ LDAPMod **mods = NULL;
+ int i;
+
+ /*
+ * Check if the modification contains copiedFrom/copyingFrom
+ * attributes.
+ */
+ slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods);
+ for (i = 0; NULL != mods && NULL != mods[i]; i++)
+ {
+ if ((strcasecmp(mods[i]->mod_type, type_copiedFrom) == 0) ||
+ (strcasecmp(mods[i]->mod_type, type_copyingFrom) == 0))
+ {
+ has_cf = 1;
+ }
+ }
+ }
+ break;
+ case OP_DELETE:
+ break;
+ case OP_MODDN:
+ break;
+ }
+
+ /* Squirrel away an optimization hint for the postop plugin */
+ opext->has_cf = has_cf;
+
+ return rc;
+}
diff --git a/ldap/servers/plugins/replication/repl_rootdse.c b/ldap/servers/plugins/replication/repl_rootdse.c
new file mode 100644
index 00000000..2bd1d8e8
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl_rootdse.c
@@ -0,0 +1,79 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#include <string.h>
+
+#include "repl.h"
+#include "cl4.h"
+#include "slapi-plugin.h"
+
+/* Forward Declartions */
+static int repl_rootdse_search (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg);
+
+int
+repl_rootdse_init()
+{
+ /* The FE DSE *must* be initialised before we get here */
+ int return_value= LDAP_SUCCESS;
+
+ slapi_config_register_callback(SLAPI_OPERATION_SEARCH,DSE_FLAG_PREOP,"",LDAP_SCOPE_BASE,"(objectclass=*)",repl_rootdse_search,NULL);
+
+ return return_value;
+}
+
+static int
+repl_rootdse_search(Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg)
+{
+
+#if 0
+ struct berval val;
+ struct berval *vals[2];
+ vals[0] = &val;
+ vals[1] = NULL;
+
+ /* machine data suffix */
+ val.bv_val = REPL_CONFIG_TOP;
+ val.bv_len = strlen( val.bv_val );
+ slapi_entry_attr_replace( e, ATTR_NETSCAPEMDSUFFIX, vals );
+
+ /* Changelog information */
+/* ONREPL because we now support multiple 4.0 changelogs we no longer publish
+ info in the rootdse */
+ if ( get_repl_backend() != NULL )
+ {
+ char buf[BUFSIZ];
+ changeNumber cnum;
+
+ /* Changelog suffix */
+ val.bv_val = changelog4_get_suffix ();
+ if ( val.bv_val != NULL )
+ {
+ val.bv_len = strlen( val.bv_val );
+ slapi_entry_attr_replace( e, "changelog", vals );
+ }
+ slapi_ch_free ((void **)&val.bv_val);
+
+ /* First change number contained in log */
+ cnum = ldapi_get_first_changenumber();
+ sprintf( buf, "%lu", cnum );
+ val.bv_val = buf;
+ val.bv_len = strlen( val.bv_val );
+ slapi_entry_attr_replace( e, "firstchangenumber", vals );
+
+ /* Last change number contained in log */
+ cnum = ldapi_get_last_changenumber();
+ sprintf( buf, "%lu", cnum );
+ val.bv_val = buf;
+ val.bv_len = strlen( val.bv_val );
+ slapi_entry_attr_replace( e, "lastchangenumber", vals );
+ }
+#endif
+
+ return SLAPI_DSE_CALLBACK_OK;
+}
+
+
+
diff --git a/ldap/servers/plugins/replication/repl_search.c b/ldap/servers/plugins/replication/repl_search.c
new file mode 100644
index 00000000..cfa21222
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl_search.c
@@ -0,0 +1,25 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#include "slapi-plugin.h"
+#include "repl.h"
+
+/* XXXggood I think we no longer need this - the mapping tree should do it for us */
+int
+legacy_preop_search( Slapi_PBlock *pb )
+{
+ int return_code = 0;
+ return return_code;
+}
+
+
+/* XXXggood I think we no longer need this - the mapping tree should do it for us */
+int
+legacy_pre_entry( Slapi_PBlock *pb )
+{
+ int return_code = 0;
+ return return_code;
+}
diff --git a/ldap/servers/plugins/replication/repl_shared.h b/ldap/servers/plugins/replication/repl_shared.h
new file mode 100644
index 00000000..0c7454ab
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl_shared.h
@@ -0,0 +1,132 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* repl_shared.h - definitions shared between 4.0 and 5.0 replication
+ modules
+ */
+
+#ifndef REPL_SHARED_H
+#define REPL_SHARED_H
+
+#include "slapi-private.h"
+#include "slapi-plugin.h"
+#include "ldif.h" /* GGOODREPL - is this cheating? */
+
+#ifdef _WIN32
+#define FILE_PATHSEP '\\'
+#else
+#define FILE_PATHSEP '/'
+#endif
+
+#define CHANGELOGDB_TRIM_INTERVAL 300 /* 5 minutes */
+
+#define CONFIG_CHANGELOG_DIR_ATTRIBUTE "nsslapd-changelogdir"
+#define CONFIG_CHANGELOG_MAXENTRIES_ATTRIBUTE "nsslapd-changelogmaxentries"
+#define CONFIG_CHANGELOG_MAXAGE_ATTRIBUTE "nsslapd-changelogmaxage"
+/* Changelog Internal Configuration Parameters -> DB related */
+#define CONFIG_CHANGELOG_DB_DBCACHESIZE "nsslapd-dbcachesize"
+#define CONFIG_CHANGELOG_DB_DURABLE_TRANSACTIONS "nsslapd-db-durable-transaction"
+#define CONFIG_CHANGELOG_DB_CHECKPOINT_INTERVAL "nsslapd-db-checkpoint-interval"
+#define CONFIG_CHANGELOG_DB_CIRCULAR_LOGGING "nsslapd-db-circular-logging"
+#define CONFIG_CHANGELOG_DB_PAGE_SIZE "nsslapd-db-page-size"
+#define CONFIG_CHANGELOG_DB_LOGFILE_SIZE "nsslapd-db-logfile-size"
+#define CONFIG_CHANGELOG_DB_MAXTXN_SIZE "nsslapd-db-max-txn"
+#define CONFIG_CHANGELOG_DB_VERBOSE "nsslapd-db-verbose"
+#define CONFIG_CHANGELOG_DB_DEBUG "nsslapd-db-debug"
+#define CONFIG_CHANGELOG_DB_TRICKLE_PERCENTAGE "nsslapd-db-trickle-percentage"
+#define CONFIG_CHANGELOG_DB_SPINCOUNT "nsslapd-db-spin-count"
+/* Changelog Internal Configuration Parameters -> Changelog Cache related */
+#define CONFIG_CHANGELOG_CACHESIZE "nsslapd-cachesize"
+#define CONFIG_CHANGELOG_CACHEMEMSIZE "nsslapd-cachememsize"
+#define CONFIG_CHANGELOG_NB_LOCK "nsslapd-db-locks"
+#define CONFIG_CHANGELOG_MAX_CONCURRENT_WRITES "nsslapd-changelogmaxconcurrentwrites"
+
+#define T_CHANGETYPESTR "changetype"
+#define T_CHANGETYPE 1
+#define T_TIMESTR "time"
+#define T_TIME 2
+#define T_DNSTR "dn"
+#define T_DN 3
+#define T_CHANGESTR "change"
+#define T_CHANGE 4
+
+#define T_ADDCTSTR "add"
+#define T_ADDCT 4
+#define T_MODIFYCTSTR "modify"
+#define T_MODIFYCT 5
+#define T_DELETECTSTR "delete"
+#define T_DELETECT 6
+#define T_MODRDNCTSTR "modrdn"
+#define T_MODRDNCT 7
+#define T_MODDNCTSTR "moddn"
+#define T_MODDNCT 8
+
+#define T_MODOPADDSTR "add"
+#define T_MODOPADD 9
+#define T_MODOPREPLACESTR "replace"
+#define T_MODOPREPLACE 10
+#define T_MODOPDELETESTR "delete"
+#define T_MODOPDELETE 11
+#define T_MODSEPSTR "-"
+#define T_MODSEP 12
+
+#define T_NEWRDNSTR "newrdn"
+#define T_NEWSUPERIORSTR ATTR_NEWSUPERIOR
+#define T_DRDNFLAGSTR "deleteoldrdn"
+
+#define T_ERR -1
+#define AWAITING_OP -1
+
+#define STATE_REFERRAL "referral"
+#define STATE_UPDATE_REFERRAL "referral on update"
+#define STATE_BACKEND "backend"
+
+#define REPL_PLUGIN_NAME "NSMMReplicationPlugin"
+/*
+ * Changed version from 1.0 to 2.0 when we switched from libdb32 to libdb33
+ * richm 20020708
+ * also changed name from REPL_PLUGIN_VERSION to CHANGELOG_DB_VERSION since we use
+ * a different version for the plugin itself and this particular version is only
+ * used for the changelog database
+*/
+/*
+ * Changed version from 2.0 to 3.0 when we switched from libdb33 to libdb41
+ * noriko 20021203
+ */
+#define CHANGELOG_DB_VERSION_PREV "3.0"
+#define CHANGELOG_DB_VERSION "4.0"
+extern char *repl_plugin_name;
+extern char *repl_plugin_name_cl;
+
+/* repl_monitor.c */
+int repl_monitor_init();
+
+/* In replutil.c */
+char ** get_cleattrs();
+unsigned long strntoul( char *from, size_t len, int base );
+void freepmods( LDAPMod **pmods );
+char *copy_berval (struct berval* from);
+void entry_print(Slapi_Entry *e);
+int copyfile(char* source, char *destination, int overwrite, int mode);
+time_t age_str2time (const char *age);
+const char* changeType2Str (int type);
+int str2ChangeType (const char *str);
+lenstr *make_changes_string(LDAPMod **ldm, char **includeattrs);
+Slapi_Mods* parse_changes_string(char *str);
+PRBool IsValidOperation (const slapi_operation_parameters *op);
+const char *map_repl_root_to_dbid(Slapi_DN *repl_root);
+PRBool is_ruv_tombstone_entry (Slapi_Entry *e);
+
+/* replication plugins */
+enum {
+ PLUGIN_LEGACY_REPLICATION,
+ PLUGIN_MULTIMASTER_REPLICATION,
+ PLUGIN_MAX
+};
+
+void* repl_get_plugin_identity (int pluginID);
+void repl_set_plugin_identity (int pluginID, void *identity);
+
+#endif
diff --git a/ldap/servers/plugins/replication/replication.def b/ldap/servers/plugins/replication/replication.def
new file mode 100644
index 00000000..e71be4f6
--- /dev/null
+++ b/ldap/servers/plugins/replication/replication.def
@@ -0,0 +1,16 @@
+; BEGIN COPYRIGHT BLOCK
+; Copyright 2001 Sun Microsystems, Inc.
+; Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+; All rights reserved.
+; END COPYRIGHT BLOCK
+;
+;
+;
+DESCRIPTION 'Netscape Directory Server 7.0 Replication Plugin'
+;CODE SHARED READ EXECUTE
+;DATA SHARED READ WRITE
+EXPORTS
+ plugin_init_debug_level @1
+ replication_legacy_plugin_init @2
+ replication_multimaster_plugin_init @3
+ repl_chain_on_update @4
diff --git a/ldap/servers/plugins/replication/replutil.c b/ldap/servers/plugins/replication/replutil.c
new file mode 100644
index 00000000..f8a23f93
--- /dev/null
+++ b/ldap/servers/plugins/replication/replutil.c
@@ -0,0 +1,1073 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+ /*
+ * replutil.c - various utility functions common to all replication methods.
+ */
+
+#include <nspr.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <errno.h>
+#ifndef _WIN32
+#include <sys/file.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <fcntl.h>
+#endif
+#ifdef OS_solaris
+#include <dlfcn.h> /* needed for dlopen and dlsym */
+#endif /* solaris: dlopen */
+#include <time.h>
+#ifdef LINUX
+#include <errno.h> /* weird use of errno */
+#endif
+
+#include "slapi-plugin.h"
+#include "repl5.h"
+#include "repl.h"
+
+typedef int (*open_fn)(const char *path, int flags, ...);
+
+/* this is set during replication plugin initialization from the plugin entry */
+static char *replpluginpath = NULL;
+static PRBool is_chain_on_update_setup(const Slapi_DN *replroot);
+
+/*
+ * All standard changeLogEntry attributes (initialized in get_cleattrs)
+ */
+static char *cleattrs[ 10 ] = { NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL };
+
+/*
+ * Function: get_cleattrs
+ *
+ * Returns: an array of pointers to attribute names.
+ *
+ * Arguments: None.
+ *
+ * Description: Initializes, if necessary, and returns an array of char *s
+ * with attribute names used for retrieving changeLogEntry
+ * entries from the directory.
+ */
+char **
+get_cleattrs()
+{
+ if ( cleattrs[ 0 ] == NULL ) {
+ cleattrs[ 0 ] = type_objectclass;
+ cleattrs[ 1 ] = attr_changenumber;
+ cleattrs[ 2 ] = attr_targetdn;
+ cleattrs[ 3 ] = attr_changetype;
+ cleattrs[ 4 ] = attr_newrdn;
+ cleattrs[ 5 ] = attr_deleteoldrdn;
+ cleattrs[ 6 ] = attr_changes;
+ cleattrs[ 7 ] = attr_newsuperior;
+ cleattrs[ 8 ] = attr_changetime;
+ cleattrs[ 9 ] = NULL;
+ }
+ return cleattrs;
+}
+
+/*
+ * Function: add_bval2mods
+ *
+ * Description: same as add_val2mods, but sticks in a bval instead.
+ * val can be null.
+ */
+void
+add_bval2mods(LDAPMod **mod, char *type, char *val, int mod_op)
+{
+ *mod = (LDAPMod *) slapi_ch_calloc(1, sizeof (LDAPMod));
+ memset (*mod, 0, sizeof(LDAPMod));
+ (*mod)->mod_op = mod_op | LDAP_MOD_BVALUES;
+ (*mod)->mod_type = slapi_ch_strdup(type);
+
+ if (val != NULL){
+ (*mod)->mod_bvalues = (struct berval **) slapi_ch_calloc(2, sizeof(struct berval *));
+ (*mod)->mod_bvalues[0] = (struct berval *) slapi_ch_malloc (sizeof(struct berval));
+ (*mod)->mod_bvalues[1] = NULL;
+ (*mod)->mod_bvalues[0]->bv_len = strlen(val);
+ (*mod)->mod_bvalues[0]->bv_val = slapi_ch_strdup(val);
+ } else {
+ (*mod)->mod_bvalues = NULL;
+ }
+}
+
+
+char*
+copy_berval (struct berval* from)
+{
+ char* s = slapi_ch_malloc (from->bv_len + 1);
+ memcpy (s, from->bv_val, from->bv_len);
+ s [from->bv_len] = '\0';
+ return s;
+}
+
+
+/*
+ * Function: entry_print
+ * Arguments: e - entry to print
+ * Returns: nothing
+ * Description: Prints the contents of an Slapi_Entry struct. Used for debugging.
+ */
+void
+entry_print( Slapi_Entry *e )
+{
+ int sz;
+ char *p;
+
+ printf( "Slapi_Entry dump:\n" );
+
+ if ( e == NULL ) {
+ printf( "Slapi_Entry is NULL\n" );
+ return;
+ }
+
+ if (( p = slapi_entry2str( e, &sz )) == NULL ) {
+ printf( "slapi_entry2str returned NULL\n" );
+ return;
+ }
+ puts( p );
+ fflush( stdout );
+ free( p );
+ return;
+}
+
+/* NSPR supports large file, but, according to dboreham, it does not work.
+ The backed has its own functions to deal with large files. I thought
+ about making them slapi function, but I don't think it makes sense because
+ server should only export function which have to do with its operation
+ and copying files is not one of them. So, instead, I made a copy of it in the
+ replication module. I will switch it to NSPR once that stuff works.
+*/
+
+int copyfile(char* source, char * destination, int overwrite, int mode)
+{
+#if defined _WIN32
+ return (0 == CopyFile(source,destination,overwrite ? FALSE : TRUE));
+#else
+#ifdef DB_USE_64LFS
+#define OPEN_FUNCTION dblayer_open_large
+#else
+#define OPEN_FUNCTION open
+#endif
+ int source_fd = -1;
+ int dest_fd = -1;
+ char *buffer = NULL;
+ int return_value = -1;
+ int bytes_to_write = 0;
+
+ /* malloc the buffer */
+ buffer = (char*) malloc(64*1024);
+ if (NULL == buffer)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "copy file: memory allocation failed\n");
+ goto error;
+ }
+ /* Open source file */
+ source_fd = OPEN_FUNCTION(source,O_RDONLY,0);
+ if (-1 == source_fd)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "copyfile: failed to open source file %s\n", source);
+ goto error;
+ }
+ /* Open destination file */
+ dest_fd = OPEN_FUNCTION(destination,O_CREAT | O_WRONLY, mode);
+ if (-1 == dest_fd)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "copyfile: failed to open destination file %s\n", destination);
+ goto error;
+ }
+ /* Loop round reading data and writing it */
+ while (1)
+ {
+ return_value = read(source_fd,buffer,64*1024);
+ if (return_value <= 0)
+ {
+ /* means error or EOF */
+ break;
+ }
+ bytes_to_write = return_value;
+ return_value = write(dest_fd,buffer,bytes_to_write);
+ if (return_value != bytes_to_write)
+ {
+ /* means error */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "copyfile: failed to write to destination file %s\n");
+ return_value = -1;
+ break;
+ }
+ }
+error:
+ if (source_fd != -1)
+ {
+ close(source_fd);
+ }
+ if (dest_fd != -1)
+ {
+ close(dest_fd);
+ }
+ if (NULL != buffer)
+ {
+ free(buffer);
+ }
+ return return_value;
+#endif
+}
+
+/* convert time from string like 1h (1 hour) to corresponding time in seconds */
+time_t
+age_str2time (const char *age)
+{
+ char *maxage;
+ char unit;
+ time_t ageval;
+
+ if (age == NULL || age[0] == '\0' || strcmp (age, "0") == 0)
+ {
+ return 0;
+ }
+
+ maxage = slapi_ch_strdup ( age );
+ unit = maxage[ strlen( maxage ) - 1 ];
+ maxage[ strlen( maxage ) - 1 ] = '\0';
+ ageval = strntoul( maxage, strlen( maxage ), 10 );
+ if ( maxage)
+ {
+ slapi_ch_free ( (void **) &maxage );
+ }
+ switch ( unit )
+ {
+ case 's':
+ break;
+ case 'm':
+ ageval *= 60;
+ break;
+ case 'h':
+ ageval *= ( 60 * 60 );
+ break;
+ case 'd':
+ ageval *= ( 24 * 60 * 60 );
+ break;
+ case 'w':
+ ageval *= ( 7 * 24 * 60 * 60 );
+ break;
+ default:
+ slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name,
+ "age_str2time: unknown unit \"%c\" "
+ "for maxiumum changelog age\n", unit );
+ ageval = -1;
+ }
+
+ return ageval;
+}
+
+const char*
+changeType2Str (int type)
+{
+ switch (type)
+ {
+ case T_ADDCT: return T_ADDCTSTR;
+ case T_MODIFYCT: return T_MODIFYCTSTR;
+ case T_MODRDNCT: return T_MODRDNCTSTR;
+ case T_DELETECT: return T_DELETECTSTR;
+ default: return NULL;
+ }
+}
+
+int
+str2ChangeType (const char *str)
+{
+ if (strcasecmp (str, T_ADDCTSTR) == 0)
+ return T_ADDCT;
+
+ if (strcasecmp (str, T_MODIFYCTSTR) == 0)
+ return T_MODIFYCT;
+
+ if (strcasecmp (str, T_MODRDNCTSTR) == 0)
+ return T_MODRDNCT;
+
+ if (strcasecmp (str, T_DELETECTSTR) == 0)
+ return T_DELETECT;
+
+ return -1;
+}
+
+lenstr *
+make_changes_string(LDAPMod **ldm, char **includeattrs)
+{
+ lenstr *l;
+ int i, j, len;
+ int skip;
+
+ /* loop through the LDAPMod struct and construct the changes attribute */
+ l = lenstr_new();
+
+ for ( i = 0; ldm[ i ] != NULL; i++ ) {
+ /* If a list of explicit attributes was given, only add those */
+ if ( NULL != includeattrs ) {
+ skip = 1;
+ for ( j = 0; includeattrs[ j ] != NULL; j++ ) {
+ if ( strcasecmp( includeattrs[ j ], ldm[ i ]->mod_type ) == 0 ) {
+ skip = 0;
+ break;
+ }
+ }
+ if ( skip ) {
+ continue;
+ }
+ }
+ switch ( ldm[ i ]->mod_op & ~LDAP_MOD_BVALUES ) {
+ case LDAP_MOD_ADD:
+ addlenstr( l, "add: " );
+ addlenstr( l, ldm[ i ]->mod_type );
+ addlenstr( l, "\n" );
+ break;
+ case LDAP_MOD_DELETE:
+ addlenstr( l, "delete: " );
+ addlenstr( l, ldm[ i ]->mod_type );
+ addlenstr( l, "\n" );
+ break;
+ case LDAP_MOD_REPLACE:
+ addlenstr( l, "replace: " );
+ addlenstr( l, ldm[ i ]->mod_type );
+ addlenstr( l, "\n" );
+ break;
+ }
+ for ( j = 0; ldm[ i ]->mod_bvalues != NULL &&
+ ldm[ i ]->mod_bvalues[ j ] != NULL; j++ ) {
+ char *buf = NULL;
+ char *bufp = NULL;
+
+ len = strlen( ldm[ i ]->mod_type );
+ len = LDIF_SIZE_NEEDED( len,
+ ldm[ i ]->mod_bvalues[ j ]->bv_len ) + 1;
+ buf = slapi_ch_malloc( len );
+ bufp = buf;
+ ldif_put_type_and_value( &bufp, ldm[ i ]->mod_type,
+ ldm[ i ]->mod_bvalues[ j ]->bv_val,
+ ldm[ i ]->mod_bvalues[ j ]->bv_len );
+ *bufp = '\0';
+
+ addlenstr( l, buf );
+
+ free( buf );
+ }
+ addlenstr( l, "-\n" );
+ }
+ return l;
+}
+
+/* note that the string get modified by ldif_parse*** functions */
+Slapi_Mods *
+parse_changes_string(char *str)
+{
+ int rc;
+ Slapi_Mods *mods;
+ Slapi_Mod mod;
+ char *line, *next;
+ char *type, *value;
+ int vlen;
+ struct berval bv;
+
+ /* allocate mods array */
+ mods = slapi_mods_new ();
+ if (mods == NULL)
+ return NULL;
+
+ slapi_mods_init (mods, 16); /* JCMREPL - ONREPL : 16 bigger than needed? */
+
+ /* parse mods */
+ next = str;
+ line = ldif_getline (&next);
+ while (line)
+ {
+ slapi_mod_init (&mod, 0);
+ while (line)
+ {
+ char * errmsg = NULL;
+
+ if (strcasecmp (line, "-") == 0)
+ {
+ if (slapi_mod_isvalid (&mod))
+ {
+ slapi_mods_add_smod (mods, &mod);
+ /* JCMREPL - ONREPL - slapi_mod_done(&mod) ??? */
+ }
+ else
+ {
+ /* ONREPL - need to cleanup */
+ }
+
+ line = ldif_getline (&next);
+ break;
+ }
+
+ rc = ldif_parse_line(line, &type, &value, &vlen, &errmsg);
+ if (rc != 0)
+ {
+ /* ONREPL - log warning */
+ if ( errmsg != NULL ) {
+ slapi_log_error( SLAPI_LOG_PARSE, repl_plugin_name, "%s", errmsg );
+ slapi_ch_free( (void**)&errmsg );
+ }
+ slapi_log_error( SLAPI_LOG_REPL, repl_plugin_name,
+ "Failed to parse the ldif line.\n");
+ continue;
+ }
+
+ if (strcasecmp (type, "add") == 0)
+ {
+ slapi_mod_set_operation (&mod, LDAP_MOD_ADD | LDAP_MOD_BVALUES);
+ }
+ else if (strcasecmp (type, "delete") == 0)
+ {
+ slapi_mod_set_operation (&mod, LDAP_MOD_DELETE | LDAP_MOD_BVALUES);
+ }
+ else if (strcasecmp (type, "replace") == 0)
+ {
+ slapi_mod_set_operation (&mod, LDAP_MOD_REPLACE | LDAP_MOD_BVALUES);
+ }
+ else /* attr: value pair */
+ {
+ /* adding first value */
+ if (slapi_mod_get_type (&mod) == NULL)
+ {
+ slapi_mod_set_type (&mod, type);
+ }
+
+ bv.bv_val = value;
+ bv.bv_len = vlen;
+
+ slapi_mod_add_value (&mod, &bv);
+ }
+
+ line = ldif_getline (&next);
+ }
+ }
+
+ return mods;
+}
+
+static void* g_plg_identity [PLUGIN_MAX];
+
+void*
+repl_get_plugin_identity (int pluginID)
+{
+ PR_ASSERT (pluginID < PLUGIN_MAX);
+ return g_plg_identity [pluginID];
+}
+
+void
+repl_set_plugin_identity (int pluginID, void *identity)
+{
+ PR_ASSERT (pluginID < PLUGIN_MAX);
+ g_plg_identity [pluginID] = identity;
+}
+
+/* this function validates operation parameters */
+PRBool
+IsValidOperation (const slapi_operation_parameters *op)
+{
+ if (op == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "IsValidOperation: NULL operation\n");
+ return PR_FALSE;
+ }
+
+ if (op->csn == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "IsValidOperation: NULL operation CSN\n");
+ return PR_FALSE;
+ }
+
+ if (op->target_address.uniqueid == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "IsValidOperation: NULL entry uniqueid\n");
+ return PR_FALSE;
+ }
+
+ if (op->target_address.dn == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "IsValidOperation: NULL entry DN\n");
+ return PR_FALSE;
+ }
+
+ switch (op->operation_type)
+ {
+ case SLAPI_OPERATION_ADD: if (op->p.p_add.target_entry == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "IsValidOperation: NULL entry for add operation\n");
+ return PR_FALSE;
+ }
+ else
+ break;
+
+ case SLAPI_OPERATION_MODIFY: if (op->p.p_modify.modify_mods == NULL ||
+ op->p.p_modify.modify_mods[0] == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "IsValidOperation: NULL mods for modify operation\n");
+ return PR_FALSE;
+ }
+ else
+ break;
+
+ case SLAPI_OPERATION_MODRDN: if (op->p.p_modrdn.modrdn_mods == NULL ||
+ op->p.p_modrdn.modrdn_mods[0] == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "IsValidOperation: NULL mods for modrdn operation\n");
+ return PR_FALSE;
+ }
+ if (op->p.p_modrdn.modrdn_newrdn == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "IsValidOperation: NULL new rdn for modrdn operation\n");
+ return PR_FALSE;
+ }
+ else
+ break;
+
+ case SLAPI_OPERATION_DELETE: break;
+
+ default: return PR_FALSE;
+ }
+
+ return PR_TRUE;
+}
+
+
+
+const char *
+map_repl_root_to_dbid(Slapi_DN *repl_root)
+{
+ const char *return_ptr;
+
+ PR_ASSERT(NULL != repl_root);
+ if (NULL != repl_root)
+ {
+ /* XXXggood get per-database ID here, when code available */
+ }
+ return_ptr = get_server_dataversion(); /* XXXggood temporary hack until we have per-database instance dbids */
+ return return_ptr;
+}
+
+
+
+PRBool
+is_ruv_tombstone_entry (Slapi_Entry *e)
+{
+ char *dn;
+ char *match;
+ PR_ASSERT (e);
+
+ dn = slapi_entry_get_dn (e);
+ PR_ASSERT (dn);
+
+ /* tombstone has rdn: nsuniqueid=ffffffff-ffffffff-ffffffff-ffffffff */
+ match = strstr (dn, RUV_STORAGE_ENTRY_UNIQUEID);
+
+ return (match != NULL);
+}
+
+LDAPControl* create_managedsait_control ()
+{
+ LDAPControl *control;
+
+ control = (LDAPControl*)slapi_ch_malloc (sizeof (LDAPControl));
+
+ control->ldctl_oid = slapi_ch_strdup (LDAP_CONTROL_MANAGEDSAIT);
+ control->ldctl_value.bv_val = NULL;
+ control->ldctl_value.bv_len = 0;
+ control->ldctl_iscritical = '\0';
+
+ return control;
+}
+
+LDAPControl* create_backend_control (Slapi_DN *sdn)
+{
+ LDAPControl *control = NULL;
+ const char *be_name = slapi_mtn_get_backend_name(sdn);
+ if (NULL != be_name) {
+ control = (LDAPControl*)slapi_ch_malloc (sizeof (LDAPControl));
+
+ control->ldctl_oid = slapi_ch_strdup ("2.16.840.1.113730.3.4.14");
+ control->ldctl_value.bv_val = strdup(be_name);
+ control->ldctl_value.bv_len = strlen (be_name);
+ control->ldctl_iscritical = 1;
+ }
+
+ return control;
+}
+
+/*
+ * HREF_CHAR_ACCEPTABLE was copied from slapd/referral.c
+ * which was copied from libldap/tmplout.c.
+ */
+/* Note: an identical function is in ../../slapd/referral.c */
+#define HREF_CHAR_ACCEPTABLE( c ) (( c >= '-' && c <= '9' ) || \
+ ( c >= '@' && c <= 'Z' ) || \
+ ( c == '_' ) || \
+ ( c >= 'a' && c <= 'z' ))
+
+/*
+ * Function: strcat_escaped
+ *
+ * Returns: nothing
+ *
+ * Description: Appends string s2 to s1, URL-escaping (%HH) unsafe
+ * characters in s2 as appropriate. This function was
+ * copied from slapd/referral.c.
+ * which was copied from libldap/tmplout.c.
+ * added const qualifier
+ *
+ * Author: MCS
+ */
+/*
+ * append s2 to s1, URL-escaping (%HH) unsafe characters
+ */
+/* Note: an identical function is in ../../slapd/referral.c */
+static void
+strcat_escaped( char *s1, const char *s2 )
+{
+ char *p, *q;
+ char *hexdig = "0123456789ABCDEF";
+
+ p = s1 + strlen( s1 );
+ for ( q = (char*)s2; *q != '\0'; ++q ) {
+ if ( HREF_CHAR_ACCEPTABLE( *q )) {
+ *p++ = *q;
+ } else {
+ *p++ = '%';
+ *p++ = hexdig[ 0x0F & ((*(unsigned char*)q) >> 4) ];
+ *p++ = hexdig[ 0x0F & *q ];
+ }
+ }
+
+ *p = '\0';
+}
+
+/*
+ This function appends the replication root to the purl referrals found
+ in the given ruv and the other given referrals, merges the lists, and sets the
+ referrals in the mapping tree node corresponding to the given sdn, which is the
+ repl_root
+ This function also sets the mapping tree state (e.g. disabled, backend, referral,
+ referral on update) - the mapping tree has very specific rules about how states
+ can be set in the presence of referrals - specifically:
+ 1) the nsslapd-referral attribute must be set before changing the state to referral
+ or referral on update
+ 2) the state must be set to backend or disabled before removing referrals
+*/
+void
+repl_set_mtn_state_and_referrals(
+ const Slapi_DN *repl_root_sdn,
+ const char *mtn_state,
+ const RUV *ruv,
+ char **ruv_referrals,
+ char **other_referrals
+)
+{
+ int rc = 0;
+ int ii = 0;
+ char **referrals_to_set = NULL;
+ PRBool chain_on_update = is_chain_on_update_setup(repl_root_sdn);
+
+ /* Fix for blackflag bug 601440: We want the new behaviour of DS,
+ ** going forward, to now be that if the nsds5replicareferral attrib
+ ** has values, it should be the only values in nsslapd-referral (as
+ ** opposed to older behaviour of concatenating with RUV-based
+ ** referrals). -jay@netscape.com
+ */
+ if (other_referrals) {
+ /* use the referrals passed in, instead of RUV-based referrals */
+ charray_merge(&referrals_to_set, other_referrals, 1);
+ /* Do copies. referrals is freed at the end */
+ }
+ else
+ {
+ /* use the referrals from the RUV */
+ ruv_referrals= (ruv ? ruv_get_referrals(ruv) : ruv_referrals);
+ if (ruv_referrals) {
+ charray_merge(&referrals_to_set, ruv_referrals, 1);
+ if (ruv) /* free referrals from ruv_get_referrals() */
+ charray_free(ruv_referrals);
+ }
+ }
+
+ /* next, add the repl root dn to each referral if not present */
+ for (ii = 0; referrals_to_set && referrals_to_set[ii]; ++ii) {
+ struct ldap_url_desc *lud = NULL;
+ int myrc = ldap_url_parse(referrals_to_set[ii], &lud);
+ /* see if the dn is already in the referral URL */
+ if (myrc == LDAP_URL_ERR_NODN || !lud || !lud->lud_dn) {
+ /* add the dn */
+ int len = strlen(referrals_to_set[ii]);
+ const char *cdn = slapi_sdn_get_dn(repl_root_sdn);
+ char *tmpref = NULL;
+ int need_slash = 0;
+ if (referrals_to_set[ii][len-1] != '/') {
+ len++; /* add another one for the slash */
+ need_slash = 1;
+ }
+ len += (strlen(cdn) * 3) + 2; /* 3 for %HH possible per char */
+ tmpref = slapi_ch_malloc(len);
+ sprintf(tmpref, "%s%s", referrals_to_set[ii], (need_slash ? "/" : ""));
+ strcat_escaped(tmpref, cdn);
+ slapi_ch_free((void **)&referrals_to_set[ii]);
+ referrals_to_set[ii] = tmpref;
+ }
+ if (lud)
+ ldap_free_urldesc(lud);
+ }
+
+ if (!referrals_to_set) { /* deleting referrals */
+ /* Set state before */
+ if (!chain_on_update) {
+ slapi_mtn_set_state(repl_root_sdn, (char *)mtn_state);
+ }
+ /* We should delete referral only if we want to set the
+ replica database in backend state mode */
+ /* if chain on update mode, go ahead and set the referrals anyway */
+ if (strcasecmp(mtn_state, STATE_BACKEND) == 0 || chain_on_update) {
+ rc = slapi_mtn_set_referral(repl_root_sdn, referrals_to_set);
+ if (rc == LDAP_NO_SUCH_ATTRIBUTE) {
+ /* we will get no such attribute (16) if we try to set the referrals to NULL if
+ there are no referrals - not an error */
+ rc = LDAP_SUCCESS;
+ }
+ }
+ } else { /* Replacing */
+ rc = slapi_mtn_set_referral(repl_root_sdn, referrals_to_set);
+ if (rc == LDAP_SUCCESS && !chain_on_update){
+ slapi_mtn_set_state(repl_root_sdn, (char *)mtn_state);
+ }
+ }
+
+ if (rc != LDAP_SUCCESS) {
+ char ebuf[BUFSIZ];
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "repl_set_mtn_referrals: could "
+ "not set referrals for replica %s: %d\n",
+ escape_string(slapi_sdn_get_dn(repl_root_sdn), ebuf), rc);
+ }
+
+ charray_free(referrals_to_set);
+ return;
+}
+
+/*
+ * This function allows to use a local backend in conjunction with
+ * a chaining backend
+ * The local ldbm backend is the replication consumer database
+ * (e.g. on a hub or consumer) - it is read-only except for supplier updates
+ * The chaining backend points to the supplier(s)
+ * This distribution logic forwards the update request to the chaining
+ * backend, and sends the search request to the local ldbm database
+ *
+ * To be able to use it one must define one ldbm backend and one chaining
+ * backend in the mapping tree node - the ldbm backend will usually
+ * already be there
+ *
+ */
+int
+repl_chain_on_update(Slapi_PBlock *pb, Slapi_DN * target_dn,
+ char **mtn_be_names, int be_count,
+ Slapi_DN * node_dn, int *mtn_be_states)
+{
+ char * requestor_dn;
+ unsigned long op_type;
+ Slapi_Operation *op;
+ int repl_op = 0;
+ int local_backend = -1; /* index of local backend */
+ int chaining_backend = -1; /* index of chain backend */
+ PRBool local_online = PR_FALSE; /* true if the local db is online */
+ PRBool chain_online = PR_FALSE; /* true if the chain db is online */
+ int ii;
+ int opid, connid;
+
+ slapi_pblock_get(pb, SLAPI_CONN_ID, &connid);
+ slapi_pblock_get(pb, SLAPI_OPERATION_ID, &opid);
+ /* first, we have to decide which backend is the local backend
+ * and which is the chaining one
+ * also find out if any are not online (e.g. during import)
+ */
+ for (ii = 0; ii < be_count; ++ii)
+ {
+ Slapi_Backend *be = slapi_be_select_by_instance_name(mtn_be_names[ii]);
+ if (slapi_be_is_flag_set(be,SLAPI_BE_FLAG_REMOTE_DATA))
+ {
+ chaining_backend = ii;
+ if (mtn_be_states[ii] == SLAPI_BE_STATE_ON)
+ {
+ chain_online = PR_TRUE;
+ }
+ }
+ else
+ {
+ local_backend = ii;
+ if (mtn_be_states[ii] == SLAPI_BE_STATE_ON)
+ {
+ local_online = PR_TRUE;
+ }
+ }
+/*
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "repl_chain_on_update: conn=%d op=%d be "
+ "%s is the %s backend and is %s\n",
+ connid, opid,
+ mtn_be_names[ii], (chaining_backend == ii) ? "chaining" : "local",
+ (mtn_be_states[ii] == SLAPI_BE_STATE_ON) ? "online" : "offline");
+*/
+ }
+
+ /* if no chaining backends are defined, just use the local one */
+ if (chaining_backend == -1) {
+ return local_backend;
+ }
+
+ slapi_pblock_get(pb, SLAPI_OPERATION, &op);
+
+ /* All internal operations go to the local backend */
+ if (operation_is_flag_set(op, OP_FLAG_INTERNAL)) {
+ return local_backend;
+ }
+
+ /* Check the operation type
+ * read-only operation will go to the local backend if online
+ */
+ op_type = slapi_op_get_type(op);
+ if (local_online &&
+ ((op_type == SLAPI_OPERATION_SEARCH) ||
+ (op_type == SLAPI_OPERATION_BIND) ||
+ (op_type == SLAPI_OPERATION_UNBIND) ||
+ (op_type == SLAPI_OPERATION_COMPARE))) {
+/*
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "repl_chain_on_update: conn=%d op=%d op is "
+ "%d: using local backend\n",
+ connid, opid, op_type);
+*/
+ return local_backend;
+ }
+
+ /* if the operation is done by directory manager
+ * use local database even for updates because it is an administrative
+ * operation
+ * remarks : one could also use an update DN in the same way
+ * to let update operation go to the local backend when they are done
+ * by specific administrator user but let all the other user
+ * go to the read-write replica
+ * also - I don't think it is possible to chain directory manager
+ */
+ slapi_pblock_get(pb, SLAPI_REQUESTOR_DN, &requestor_dn);
+ if (slapi_dn_isroot(requestor_dn)) {
+/*
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "repl_chain_on_update: conn=%d op=%d requestor "
+ "is root: using local backend\n", connid, opid);
+*/
+ return local_backend;
+ }
+
+ /* if the operation is a replicated operation
+ * use local database even for updates to avoid infinite loops
+ */
+ slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &repl_op);
+ if (repl_op) {
+/*
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "repl_chain_on_update: conn=%d op=%d op is "
+ "replicated: using local backend\n", connid, opid);
+*/
+ return local_backend;
+ }
+
+ /* all other case (update while not directory manager) :
+ * or any normal non replicated client operation while local is disabled (import) :
+ * use the chaining backend
+ */
+/*
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "repl_chain_on_update: conn=%d op=%d using "
+ "chaining backend\n", connid, opid);
+*/
+ return chaining_backend;
+}
+
+int
+repl_enable_chain_on_update(Slapi_DN *suffix)
+{
+ /* Submit a Modify operation to add the distribution function to the mapping tree
+ node for the given suffix */
+ slapi_mods smods;
+ Slapi_Operation *op = NULL;
+ int operation_result;
+ Slapi_PBlock *pb= slapi_pblock_new();
+ char *mtnnodedn;
+
+ slapi_mods_init(&smods,2);
+
+ /* need path and file name of the replication plugin here */
+ slapi_mods_add_string(&smods, LDAP_MOD_ADD, "nsslapd-distribution-plugin", replpluginpath);
+ slapi_mods_add_string(&smods, LDAP_MOD_ADD, "nsslapd-distribution-funct", "repl_chain_on_update");
+
+ /* need DN of mapping tree node here */
+ mtnnodedn = slapi_get_mapping_tree_node_configdn(suffix);
+ slapi_modify_internal_set_pb(
+ pb,
+ mtnnodedn,
+ slapi_mods_get_ldapmods_byref(&smods), /* JCM cast */
+ NULL, /*Controls*/
+ NULL, /*uniqueid*/
+ repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION),
+ 0);
+
+ slapi_modify_internal_pb(pb);
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &operation_result);
+ slapi_ch_free_string(&mtnnodedn);
+ slapi_pblock_destroy(pb);
+ switch(operation_result)
+ {
+ case LDAP_SUCCESS:
+ /* OK, everything is fine. */
+ break;
+ default:
+ PR_ASSERT(0); /* JCMREPL FOR DEBUGGING */
+ }
+ slapi_mods_done(&smods);
+
+ return operation_result;
+}
+
+int
+repl_disable_chain_on_update(Slapi_DN *suffix)
+{
+ /* Submit a Modify operation to remove the distribution function from the mapping tree
+ node for the given suffix */
+ slapi_mods smods;
+ Slapi_Operation *op = NULL;
+ int operation_result;
+ Slapi_PBlock *pb= slapi_pblock_new();
+ char *mtnnodedn;
+
+ slapi_mods_init(&smods,2);
+
+ slapi_mods_add_modbvps(&smods, LDAP_MOD_DELETE, "nsslapd-distribution-plugin", NULL);
+ slapi_mods_add_modbvps(&smods, LDAP_MOD_DELETE, "nsslapd-distribution-funct", NULL);
+
+ /* need DN of mapping tree node here */
+ mtnnodedn = slapi_get_mapping_tree_node_configdn(suffix);
+ slapi_modify_internal_set_pb(
+ pb,
+ mtnnodedn,
+ slapi_mods_get_ldapmods_byref(&smods), /* JCM cast */
+ NULL, /*Controls*/
+ NULL, /*uniqueid*/
+ repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION),
+ 0);
+
+ slapi_modify_internal_pb(pb);
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &operation_result);
+ slapi_ch_free_string(&mtnnodedn);
+ slapi_pblock_destroy(pb);
+ switch(operation_result)
+ {
+ case LDAP_SUCCESS:
+ /* OK, everything is fine. */
+ break;
+ default:
+ PR_ASSERT(0); /* JCMREPL FOR DEBUGGING */
+ }
+ slapi_mods_done(&smods);
+
+ return operation_result;
+}
+
+static PRBool
+is_chain_on_update_setup(const Slapi_DN *replroot)
+{
+ /* Do an internal search of the mapping tree node to see if chain on update is setup
+ for this replica
+ - has two backends
+ - has a distribution function
+ - has a distribution plugin
+ - one of the backends is a ldbm database
+ - one of the backends is a chaining database
+ */
+ static char* attrs[] = { "nsslapd-backend",
+ "nsslapd-distribution-plugin", "nsslapd-distribution-funct",
+ NULL };
+ int operation_result;
+ Slapi_PBlock *pb= slapi_pblock_new();
+ char *mtnnodedn = slapi_get_mapping_tree_node_configdn(replroot);
+ PRBool retval = PR_FALSE;
+
+ slapi_search_internal_set_pb(
+ pb,
+ mtnnodedn,
+ LDAP_SCOPE_BASE,
+ "objectclass=*",
+ attrs, /*attrs*/
+ 0, /*attrsonly*/
+ NULL, /*Controls*/
+ NULL, /*uniqueid*/
+ repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION),
+ 0);
+ slapi_search_internal_pb(pb);
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &operation_result);
+ switch(operation_result)
+ {
+ case LDAP_SUCCESS:
+ {
+ Slapi_Entry **entries= NULL;
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries);
+ if(entries!=NULL && entries[0]!=NULL)
+ {
+ Slapi_Entry *e = entries[0];
+
+ char **backends = slapi_entry_attr_get_charray(e, "nsslapd-backend");
+ char *plg = slapi_entry_attr_get_charptr(e, "nsslapd-distribution-plugin");
+ char *func = slapi_entry_attr_get_charptr(e, "nsslapd-distribution-funct");
+
+ if (backends && backends[0] && backends[1] && plg && func)
+ {
+ /* all the necessary attrs are present - check to see if we
+ have one chaining backend */
+ Slapi_Backend *be0 = slapi_be_select_by_instance_name(backends[0]);
+ Slapi_Backend *be1 = slapi_be_select_by_instance_name(backends[1]);
+ PRBool foundchain0 = slapi_be_is_flag_set(be0,SLAPI_BE_FLAG_REMOTE_DATA);
+ PRBool foundchain1 = slapi_be_is_flag_set(be1,SLAPI_BE_FLAG_REMOTE_DATA);
+ retval = (foundchain0 || foundchain1) &&
+ !(foundchain0 && foundchain1); /* 1 (but not both) backend is chaining */
+ }
+ slapi_ch_array_free(backends);
+ slapi_ch_free_string(&plg);
+ slapi_ch_free_string(&func);
+ }
+ else /* could not find mapping tree entry - assume not set up */
+ {
+ }
+ }
+ break;
+ default: /* search error - assume not set up */
+ break;
+ }
+ slapi_ch_free_string(&mtnnodedn);
+ slapi_free_search_results_internal(pb);
+ slapi_pblock_destroy(pb);
+
+ return retval;
+}
+
+void
+repl_set_repl_plugin_path(const char *path)
+{
+ replpluginpath = slapi_ch_strdup(path);
+}
diff --git a/ldap/servers/plugins/replication/tests/dnp_sim.c b/ldap/servers/plugins/replication/tests/dnp_sim.c
new file mode 100644
index 00000000..ff524988
--- /dev/null
+++ b/ldap/servers/plugins/replication/tests/dnp_sim.c
@@ -0,0 +1,1033 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* dnp_simulation.c - this file varifies the correctness of dnp algorithm
+ by generating random sequences of operations, applying
+ the algorithm and outputing the result
+
+ usage: dnp_sim [-h] [-n <number of simulations> ] [-v] [-f <output file>]
+ -h - print usage information.
+ -n <number of simulations> - how many simulations to perform; default - 1.
+ -v - verbose mode (prints full entry state after each operation execution)
+ -f <output file> - file where results are stored; by default results are
+ printed to the screen.
+ -o <op file> - file that contains operation sequence to execute; by default,
+ random sequence is generated.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <memory.h>
+#include <string.h>
+#include <time.h>
+
+#define MAX_OPS 12 /* maximum number of operations in a simulation */
+#define MAX_VALS 10 /* maximum number of values is entry or dn */
+#define NOT_PRESENT -1
+
+/* data types */
+typedef struct value_state
+{
+ int value_index; /* value */
+ int presense_csn; /* last time at which we know the value was present */
+ int distinguished_csn; /* last time at which we know the value was distinguished */
+ int delete_csn; /* last attempt to delete this value */
+ int non_distinguished_csns [MAX_OPS];/* list of times at which value became non-distinguished */
+ int present; /* flag that tells whether the value iscurrently present */
+} Value_State;
+
+typedef struct entry_state
+{
+ int dn_index;
+ int dn_csn;
+ Value_State values[MAX_VALS]; /* values of the attribute */
+ int attr_delete_csn; /* last attempt to delete the entire attribute */
+} Entry_State;
+
+typedef enum
+{
+ OP_ADD_VALUE,
+ OP_RENAME_ENTRY,
+ OP_DELETE_VALUE,
+ OP_DELETE_ATTR,
+ OP_END
+} Operation_Type;
+
+typedef struct operation
+{
+ Operation_Type type; /* operation type */
+ int csn; /* operation type */
+ int value_index; /* value to add, remove or rename from */
+ int old_dn_index; /* new dn - rename only */
+}Operation;
+
+typedef struct simulator_params
+{
+ int runs; /* number of runs */
+ FILE *fout; /* output file */
+ int value_count; /* number of values */
+ int verbose; /* verbose mode */
+ Operation *ops; /* operation sequence to apply */
+ int op_count;
+}Simulator_Params;
+
+
+/* gloabl data */
+Simulator_Params sim;
+char *g_values[] =
+{
+ "v",
+ "u",
+ "w",
+ NULL
+};
+
+/* forward declarations */
+
+/* initialization */
+void process_cmd (int argc, char **argv);
+void set_default_sim_params ();
+int count_values ();
+void parse_operations_file (char *name);
+void parse_operation (char *line, int pos);
+int value2index (char *value);
+void print_usage ();
+
+/* simulation run */
+void run_simulation ();
+void generate_operations (Operation **ops, int *op_count);
+void generate_operation (Operation *op, int csn, int *last_dn_index);
+int* generate_operation_order (int op_count, int seq_num);
+void apply_operation_sequence (Operation *ops, int op_count, int *order, Entry_State *entry);
+void init_entry_state (Entry_State *entry);
+void init_value_state (Value_State *val, int seq_num);
+void apply_operation (Entry_State *entry, Operation *op);
+void free_operations (Operation **ops);
+int ** new_perm_table (int op_count);
+void free_perm_table (int ***perm_table, int op_count);
+int get_perm_count (int op_count);
+void generate_perm_table (int *elements, int element_count, int static_part,
+ int **perm_table);
+void apply_add_operation (Entry_State *entry, Operation *op);
+void apply_value_delete_operation (Entry_State *entry, Operation *op);
+void apply_attr_delete_operation (Entry_State *entry, Operation *op);
+void apply_rename_operation (Entry_State *entry, Operation *op);
+void make_value_distinguished (int op_csn, Entry_State *entry, int value_index);
+void make_value_non_distinguished (int op_csn, Entry_State *entry, int value_index);
+void purge_value_state (Value_State *value);
+void purge_non_distinguished_csns (Value_State *value);
+void resolve_value_state (Entry_State *entry, int value_index);
+int value_distinguished_at_delete (Value_State *value, int attr_delete_csn);
+int compare_entry_state (Entry_State *entry1, Entry_State *entry2, int run);
+
+/* data tracing */
+void dump_operations (Operation *ops, int op_count, int *order);
+void dump_operation (Operation *op);
+void dump_perm_table (int **perm_table, int op_count);
+void dump_entry_state (Entry_State *entry);
+void dump_value_state (Value_State *value);
+void dump_list (int *list);
+
+/* misc functions */
+int max_val (int a, int b);
+int is_list_empty (int *list);
+int min_list_val (int *list);
+int list_equal (int *list1, int *list2);
+
+int main (int argc, char **argv)
+{
+ int i;
+
+ process_cmd (argc, argv);
+
+ for (i = 0; i < sim.runs; i++)
+ {
+ fprintf (sim.fout, "*******running simulation #%d ...\n\n", i+1);
+ run_simulation ();
+ fprintf (sim.fout, "\n*******done with simulation #%d ...\n\n", i+1);
+ }
+
+ if (sim.fout != stdout)
+ fclose (sim.fout);
+
+ return 0;
+}
+
+void process_cmd (int argc, char **argv)
+{
+ int i;
+
+ set_default_sim_params ();
+
+ if (argc == 1)
+ {
+ return;
+ }
+
+ if (strcmp (argv[1], "-h") == 0) /* print help */
+ {
+ print_usage ();
+ exit (0);
+ }
+
+ i = 1;
+ while (i < argc)
+ {
+ if (strcmp (argv[i], "-v") == 0) /* verbose mode */
+ {
+ sim.verbose = 1;
+ i ++;
+ }
+ else if (strcmp (argv[i], "-n") == 0)
+ {
+ if (i < argc - 1)
+ {
+ int runs = atoi (argv[i + 1]);
+ if (runs > 0)
+ sim.runs = runs;
+ i+=2;
+ }
+ else
+ {
+ /* ONREPL print warning */
+ i++;
+ }
+ }
+ else if (strcmp (argv[i], "-f") == 0) /* output file */
+ {
+ if (i < argc - 1)
+ {
+ FILE *f = fopen (argv[i + 1], "w");
+ if (f == 0)
+ {
+ printf ("failed to open output file; error - %s, using stdout\n",
+ strerror(errno));
+ }
+ else
+ {
+ /* ONREPL print warning */
+ sim.fout = f;
+ }
+
+ i += 2;
+ }
+ else
+ i++;
+ }
+ else if (strcmp (argv[i], "-o") == 0) /* file with operation sequence */
+ {
+ if (i < argc - 1)
+ {
+ parse_operations_file (argv[i+1]);
+ i += 2;
+ }
+ else
+ {
+ /* ONREPL print warning */
+ i ++;
+ }
+ }
+ else /* unknown option */
+ {
+ printf ("unknown option - %s; ignored\n", argv[i]);
+ i ++;
+ }
+
+ }
+}
+
+void set_default_sim_params ()
+{
+ memset (&sim, 0, sizeof (sim));
+ sim.runs = 1;
+ sim.fout = stdout;
+ sim.value_count = count_values ();
+}
+
+/* file format: <op count>
+ add <value>
+ delete <value>
+ delete attribute
+ rename <value> to <value>
+ */
+void parse_operations_file (char *name)
+{
+ FILE *file = fopen (name, "r");
+ char line [256];
+ int i;
+
+ if (file == NULL)
+ {
+ printf ("failed to open operations file %s: error = %d\n", name, errno);
+ print_usage ();
+ exit (1);
+ }
+
+ i = 0;
+ while (fgets (line, sizeof (line), file))
+ {
+ if (i == 0)
+ {
+ /* read operation count */
+ sim.op_count = atoi (line);
+ if (sim.op_count < 1 || sim.op_count > MAX_OPS/2)
+ {
+ printf ("invalid operation count - %d; value must be between 1 and %d\n",
+ sim.op_count, MAX_OPS/2);
+ print_usage ();
+ exit (1);
+ }
+ else
+ {
+ sim.ops = (Operation*)malloc (sim.op_count * sizeof (Operation));
+ }
+ }
+ else
+ {
+ if (strlen (line) == 0) /* skip empty lines */
+ continue;
+ parse_operation (line, i);
+ }
+
+ i ++;
+ }
+
+}
+
+void parse_operation (char *line, int i)
+{
+ sim.ops [i - 1].csn = i;
+
+ if (line[strlen(line) - 1] == '\n')
+ line[strlen(line) - 1] = '\0';
+
+ if (strncmp (line, "add", 3) == 0)
+ {
+ sim.ops [i - 1].type = OP_ADD_VALUE;
+ sim.ops [i - 1].value_index = value2index (&line[4]);
+ }
+ else if (strncmp (line, "delete attribute", 16) == 0)
+ {
+ sim.ops [i - 1].type = OP_DELETE_ATTR;
+ }
+ else if (strncmp (line, "delete", 6) == 0)
+ {
+ sim.ops [i - 1].type = OP_DELETE_VALUE;
+ sim.ops [i - 1].value_index = value2index (&line[7]);
+ }
+ else if (strncmp (line, "rename", 6) == 0)
+ {
+ char *tok;
+ sim.ops [i - 1].type = OP_RENAME_ENTRY;
+ /* strtok() is not MT safe, but it is okay to call here because this is a program test */
+ tok = strtok (&line[7], " ");
+ sim.ops [i - 1].old_dn_index = value2index (tok);
+ /* skip to */
+ tok = strtok (NULL, " ");
+ tok = strtok (NULL, " ");
+ sim.ops [i - 1].value_index = value2index (tok);
+ }
+ else
+ {
+ /* invalid line */
+ printf ("invalid operation: %s\n", line);
+ exit (1);
+ }
+}
+
+int value2index (char *value)
+{
+ int i;
+
+ for (i = 0; i < sim.value_count; i++)
+ {
+ if (strcmp (g_values[i], value) == 0)
+ return i;
+ }
+
+ return -1;
+}
+
+void print_usage ()
+{
+ printf ("usage: dnp_sim [-h] [-n <number of simulations> ] [-v] [-f <output file>]\n"
+ "\t-h - print usage information\n"
+ "\t-n <number of simulations>; default - 1\n"
+ "\t-v - verbose mode\n"
+ "\t-f <output file> - by default, results are printed to the screen\n"
+ "\t-o <op file> - file that contains operation sequence to execute;\n"
+ "\tby default, random sequence is generated.\n");
+}
+
+int count_values ()
+{
+ int i;
+
+ for (i = 0; g_values[i]; i++);
+
+ return i;
+}
+
+void run_simulation ()
+{
+ int *order;
+ int i;
+ int perm_count;
+ Entry_State entry_first, entry_current;
+ int error = 0;
+
+ init_entry_state (&entry_first);
+ fprintf (sim.fout, "initial entry state :\n");
+ dump_entry_state (&entry_first);
+
+ if (sim.ops == NULL)
+ {
+ generate_operations (&sim.ops, &sim.op_count);
+ }
+ fprintf (sim.fout, "initial operation set:\n");
+ dump_operations (sim.ops, sim.op_count, NULL/* order */);
+
+ perm_count = get_perm_count (sim.op_count);
+ for (i = 0; i < perm_count; i++)
+ {
+ fprintf (sim.fout, "--------------------------------\n");
+ fprintf (sim.fout, "simulation run %d\n", i + 1);
+ fprintf (sim.fout, "--------------------------------\n");
+ order = generate_operation_order (sim.op_count, i);
+ if (i == 0)
+ apply_operation_sequence (sim.ops, sim.op_count, order, &entry_first);
+ else
+ {
+ apply_operation_sequence (sim.ops, sim.op_count, order, &entry_current);
+ error |= compare_entry_state (&entry_first, &entry_current, i + 1);
+ }
+ }
+
+ switch (error)
+ {
+ case 0: fprintf (sim.fout, "all runs left the entry in the same state\n");
+ break;
+ case 1: fprintf (sim.fout, "while value presence is consistent across all runs, "
+ "the exact state does not match\n");
+ break;
+ case 3: fprintf (sim.fout, "the runs left entries in an inconsistent state\n");
+ break;
+ }
+
+ free_operations (&sim.ops);
+}
+
+void generate_operations (Operation **ops, int *op_count)
+{
+ int i;
+ int last_dn_index = 0;
+
+ /* generate number operations in the sequence */
+ *op_count = slapi_rand () % (MAX_OPS / 2) + 1;
+ *ops = (Operation *)malloc (*op_count * sizeof (Operation));
+
+ for (i = 0; i < *op_count; i ++)
+ {
+ generate_operation (&((*ops)[i]), i + 1, &last_dn_index);
+ }
+}
+
+void generate_operation (Operation *op, int csn, int *last_dn_index)
+{
+ /* generate operation type */
+ op->type = slapi_rand () % OP_END;
+
+ /* generate value to which operation applies */
+ op->value_index = slapi_rand () % sim.value_count;
+
+ op->csn = csn;
+
+ /* generate new distinguished value */
+ if (op->type == OP_RENAME_ENTRY)
+ {
+ op->old_dn_index = *last_dn_index;
+ while (op->value_index == op->old_dn_index)
+ op->value_index = slapi_rand () % sim.value_count;
+ *last_dn_index = op->value_index;
+ }
+}
+
+int* generate_operation_order (int op_count, int seq_num)
+{
+ static int **perm_table = NULL;
+
+ /* first request - generate pemutation table */
+ if (seq_num == 0)
+ {
+ int elements [MAX_OPS];
+ int i;
+
+ if (perm_table)
+ free_perm_table (&perm_table, op_count);
+ perm_table = new_perm_table (op_count);
+
+ for (i = 0; i < op_count; i++)
+ elements [i] = i;
+
+ generate_perm_table (elements, op_count, 0 /* static part */,
+ perm_table);
+ /* dump_perm_table (perm_table, op_count);*/
+ }
+
+ return perm_table [seq_num];
+}
+
+void apply_operation_sequence (Operation *ops, int op_count, int *order, Entry_State *entry)
+{
+ int i;
+
+ init_entry_state (entry);
+
+ if (!sim.verbose)
+ {
+ if (!sim.verbose)
+ {
+ fprintf (sim.fout, "operation_sequence for this run:\n");
+ dump_operations (ops, op_count, order);
+ }
+ }
+
+ for (i = 0; i < op_count; i++)
+ {
+ apply_operation (entry, &(ops [order[i]]));
+ }
+
+ if (!sim.verbose)
+ {
+ fprintf (sim.fout, "final entry state :\n");
+ dump_entry_state (entry);
+ }
+
+}
+
+void init_entry_state (Entry_State *entry)
+{
+ int i;
+
+ memset (entry, 0, sizeof (*entry));
+ entry->attr_delete_csn = NOT_PRESENT;
+
+ for (i = 0; i < sim.value_count; i++)
+ {
+ init_value_state (&(entry->values[i]), i);
+ }
+}
+
+void init_value_state (Value_State *val, int seq_num)
+{
+ memset (val, 0, sizeof (*val));
+ val->value_index = seq_num;
+ val->present = 1;
+ val->delete_csn = NOT_PRESENT;
+ if (seq_num > 0) /* only first value is distinguished */
+ val->distinguished_csn = -1;
+}
+
+void apply_operation (Entry_State *entry, Operation *op)
+{
+ switch (op->type)
+ {
+ case OP_ADD_VALUE: apply_add_operation (entry, op);
+ break;
+
+ case OP_DELETE_VALUE: apply_value_delete_operation (entry, op);
+ break;
+
+ case OP_DELETE_ATTR: apply_attr_delete_operation (entry, op);
+ break;
+
+ case OP_RENAME_ENTRY: apply_rename_operation (entry, op);
+ break;
+ }
+
+ if (sim.verbose)
+ {
+ fprintf (sim.fout, "operation: ");
+ dump_operation (op);
+ fprintf (sim.fout, "\n");
+ dump_entry_state (entry);
+ }
+}
+
+void free_operations (Operation **ops)
+{
+ free (*ops);
+ *ops = NULL;
+}
+
+int **new_perm_table (int op_count)
+{
+ int i;
+ int **perm_table;
+ int perm_count = get_perm_count (op_count);
+
+ perm_table = (int**)malloc (perm_count * sizeof (int*));
+ for (i = 0; i < perm_count; i ++)
+ perm_table [i] = (int*) malloc (op_count * sizeof (int));
+
+ return perm_table;
+}
+
+void free_perm_table (int ***perm_table, int op_count)
+{
+ int i;
+ int perm_count = get_perm_count (op_count);
+
+ for (i = 0; i < perm_count; i ++)
+ free ((*perm_table)[i]);
+
+ free (*perm_table);
+ *perm_table = NULL;
+}
+
+void generate_perm_table (int *elements, int element_count, int static_part,
+ int **perm_table)
+{
+ int i;
+ int elements_copy [MAX_OPS];
+ int start_pos;
+
+ if (element_count - 1 == static_part)
+ {
+ memcpy (*perm_table, elements, element_count * sizeof (int));
+ return;
+ }
+
+ start_pos = 0;
+ for (i = 0; i < element_count - static_part; i ++)
+ {
+ memcpy (elements_copy, elements, element_count * sizeof (int));
+ elements_copy [static_part] = elements [static_part + i];
+ elements_copy [static_part + i] = elements [static_part];
+ generate_perm_table (elements_copy, element_count, static_part + 1,
+ &perm_table [start_pos]);
+ start_pos += get_perm_count (element_count - static_part - 1);
+ }
+}
+
+int get_perm_count (int op_count)
+{
+ int i;
+ int perm_count = 1;
+
+ for (i = 2; i <= op_count; i ++)
+ perm_count *= i;
+
+ return perm_count;
+}
+
+void apply_add_operation (Entry_State *entry, Operation *op)
+{
+ if (entry->values[op->value_index].presense_csn < op->csn)
+ {
+ entry->values[op->value_index].presense_csn = op->csn;
+ entry->values[op->value_index].present = 1;
+ resolve_value_state (entry, op->value_index);
+ }
+}
+
+void apply_value_delete_operation (Entry_State *entry, Operation *op)
+{
+ if (entry->values[op->value_index].delete_csn < op->csn)
+ {
+ entry->values[op->value_index].delete_csn = op->csn;
+ resolve_value_state (entry, op->value_index);
+ }
+}
+
+void apply_attr_delete_operation (Entry_State *entry, Operation *op)
+{
+ int i;
+
+ if (entry->attr_delete_csn < op->csn)
+ {
+ entry->attr_delete_csn = op->csn;
+
+ for (i = 0; i < sim.value_count; i++)
+ {
+ resolve_value_state (entry, i);
+ }
+ }
+}
+
+void apply_rename_operation (Entry_State *entry, Operation *op)
+{
+ if (entry->dn_csn < op->csn)
+ {
+ entry->dn_index = op->value_index;
+ entry->dn_csn = op->csn;
+ }
+
+ make_value_non_distinguished (op->csn, entry, op->old_dn_index);
+ make_value_distinguished (op->csn, entry, op->value_index);
+}
+
+void make_value_distinguished (int op_csn, Entry_State *entry, int value_index)
+{
+ Value_State *value = &(entry->values[value_index]);
+
+ if (value->distinguished_csn < op_csn)
+ {
+ value->distinguished_csn = op_csn;
+
+ if (value->presense_csn < op_csn)
+ {
+ value->present = 1;
+ value->presense_csn = op_csn;
+ }
+
+ resolve_value_state (entry, value_index);
+ }
+}
+
+void make_value_non_distinguished (int op_csn, Entry_State *entry, int value_index)
+{
+ int i = 0;
+ int index;
+ Value_State *value = &(entry->values[value_index]);
+
+ if (op_csn < value->distinguished_csn)
+ return;
+
+ /* insert into sorted list */
+ while (value->non_distinguished_csns[i] && value->non_distinguished_csns[i] < op_csn)
+ i++;
+
+ if (value->non_distinguished_csns[i] == 0)
+ value->non_distinguished_csns[i] = op_csn;
+ else
+ {
+ index = i;
+
+ while (value->non_distinguished_csns[i])
+ i++;
+
+ memcpy (&(value->non_distinguished_csns[index + 1]),
+ &(value->non_distinguished_csns[index]), (i - index) * sizeof (int));
+
+ value->non_distinguished_csns[index] = op_csn;
+ }
+
+ resolve_value_state (entry, value_index);
+}
+
+void purge_value_state (Value_State *value)
+{
+ /* value state information can be purged once a value was
+ readed/made distinguished because at that point we know that the value
+ existed/was distinguished */
+
+ purge_non_distinguished_csns (value);
+
+ if (value->delete_csn < max_val (value->distinguished_csn, value->presense_csn))
+ value->delete_csn = NOT_PRESENT;
+}
+
+void purge_non_distinguished_csns (Value_State *value)
+{
+ int i = 0;
+ int index;
+
+ while (value->non_distinguished_csns[i] &&
+ value->non_distinguished_csns[i] < value->distinguished_csn)
+ i ++;
+
+ if (i > 0)
+ {
+ index = i-1;
+ while (value->non_distinguished_csns[i])
+ i ++;
+
+ if (i > index + 1)
+ {
+ memcpy (value->non_distinguished_csns, &value->non_distinguished_csns[index+1],
+ (i - index - 1) * sizeof (int));
+ memset (&(value->non_distinguished_csns[index+1]), 0, sizeof (int) * (i - index - 1));
+ }
+ else
+ {
+ memset (value->non_distinguished_csns, 0, sizeof (int) * i);
+ }
+ }
+}
+
+int is_list_empty (int *list)
+{
+ return (list[0] == 0);
+}
+
+int min_list_val (int *list)
+{
+ return (list [0]);
+}
+
+void resolve_value_state (Entry_State *entry, int value_index)
+{
+ Value_State *value = &(entry->values[value_index]);
+
+ purge_value_state (value);
+
+ /* no deletes that effect the state */
+ if (max_val (value->delete_csn, entry->attr_delete_csn) <
+ max_val (value->distinguished_csn, value->presense_csn))
+ return;
+
+ if (value->present) /* check if it should be removed based on the current state */
+ {
+ if (!value_distinguished_at_delete (value, entry->attr_delete_csn))
+ {
+ /* note that we keep presence csn because we might have to restore
+ the value in the future */
+ value->present = 0;
+ }
+ }
+ else /* not present - check if it should be restored */
+ {
+ if (value_distinguished_at_delete (value, entry->attr_delete_csn))
+ {
+ value->present = 1;
+ }
+ }
+}
+
+/* Note we can't trim distinguished_csn (even during regular trimming)
+ because in some cases we would not be able to figure out whether
+ a value was distinguished or not at the time of deletion.
+
+ Example 1: Example2:
+ csn order operation csn order operation
+ 1 1 make V distinguished 1 1 make V distinguished
+ 3 3 delete V 2 2 make V non distinguished
+ 4 2 make V non-distinguished 3 4 delete V
+ 4 3 make V non distinguished (on another server)
+
+ if the csns up to 2 were triimed, when delete operation is received, the state
+ is exactly the same in both examples but in example one delete should not go
+ through while in example 2 it should
+
+ */
+int value_distinguished_at_delete (Value_State *value, int attr_delete_csn)
+{
+ if (value->distinguished_csn >= 0 &&
+ (is_list_empty (value->non_distinguished_csns) ||
+ min_list_val (value->non_distinguished_csns) >
+ max_val (value->delete_csn, attr_delete_csn)))
+ return 1;
+ else
+ return 0;
+}
+
+int compare_entry_state (Entry_State *entry1, Entry_State *entry2, int run)
+{
+ int j;
+ int error = 0;
+
+ /* first - quick check for present / not present */
+ for (j = 0; j < sim.value_count; j++)
+ {
+ if (entry1->values[j].present != entry2->values[j].present)
+ {
+ fprintf (sim.fout,
+ "value %s is %s present in the first run but %s present in the %d run\n",
+ g_values[j], entry1->values[j].present ? "" : "not",
+ entry2->values[j].present ? "" : "not", run);
+ error = 1;
+ }
+ }
+
+ if (error)
+ return 3;
+
+ /* compare value state */
+ error = 0;
+ if (entry1->attr_delete_csn != entry2->attr_delete_csn)
+ {
+ fprintf (sim.fout, "attribute delete csn is %d for run 1 "
+ "but is %d for run %d\n", entry1->attr_delete_csn,
+ entry2->attr_delete_csn, run);
+ error = 1;
+ }
+
+ for (j = 0; j < sim.value_count; j++)
+ {
+ if (entry1->values[j].presense_csn != entry2->values[j].presense_csn)
+ {
+ fprintf (sim.fout, "presence csn for value %s is %d in run 1 "
+ "but is %d in run %d\n", g_values[j], entry1->values[j].presense_csn,
+ entry2->values[j].presense_csn, run);
+ error = 1;
+ }
+
+ if (entry1->values[j].distinguished_csn != entry2->values[j].distinguished_csn)
+ {
+ fprintf (sim.fout, "distinguished csn for value %s is %d in run 1 "
+ "but is %d in run %d\n", g_values[j], entry1->values[j].distinguished_csn,
+ entry2->values[j].distinguished_csn, run);
+ error = 1;
+ }
+
+ if (entry1->values[j].delete_csn != entry2->values[j].delete_csn)
+ {
+ fprintf (sim.fout, "delete csn for value %s is %d in run 1 "
+ "but is %d in run %d\n", g_values[j], entry1->values[j].delete_csn,
+ entry2->values[j].delete_csn, run);
+ error = 1;
+ }
+
+ if (!list_equal (entry1->values[j].non_distinguished_csns,
+ entry2->values[j].non_distinguished_csns))
+ {
+ fprintf (sim.fout, "pending list mismatch for valye %s in runs 1 and %d\n",
+ g_values[j], run);
+ dump_list (entry1->values[j].non_distinguished_csns);
+ dump_list (entry2->values[j].non_distinguished_csns);
+ }
+ }
+
+ if (error != 0)
+ {
+ return 1;
+ }
+ else
+ return 0;
+}
+
+void dump_operations (Operation *ops, int op_count, int *order)
+{
+ int index;
+ int i;
+
+ for (i = 0; i < op_count; i ++)
+ {
+ if (order == NULL) /* current order */
+ index = i;
+ else
+ index = order [i];
+
+ dump_operation (&ops[index]);
+ }
+
+ fprintf (sim.fout, "\n");
+}
+
+void dump_operation (Operation *op)
+{
+ switch (op->type)
+ {
+ case OP_ADD_VALUE:
+ fprintf (sim.fout, "\t%d add value %s\n", op->csn,
+ g_values [op->value_index]);
+ break;
+ case OP_DELETE_VALUE:
+ fprintf (sim.fout, "\t%d delete value %s\n", op->csn,
+ g_values [op->value_index]);
+ break;
+ case OP_DELETE_ATTR:
+ fprintf (sim.fout, "\t%d delete attribute\n", op->csn);
+ break;
+ case OP_RENAME_ENTRY:
+ fprintf (sim.fout, "\t%d rename entry from %s to %s\n", op->csn,
+ g_values [op->old_dn_index], g_values [op->value_index]);
+ break;
+ }
+}
+
+void dump_perm_table (int **perm_table, int op_count)
+{
+ int i, j;
+ int perm_count = get_perm_count (op_count);
+
+ for (i = 0; i < op_count; i++)
+ {
+ for (j = 0; j < perm_count; j++)
+ {
+ fprintf (sim.fout, "%d ", perm_table [j][i]);
+ }
+
+ fprintf (sim.fout, "\n");
+ }
+}
+
+void dump_entry_state (Entry_State *entry)
+{
+ int i;
+ fprintf (sim.fout, "\tentry dn: %s; dn csn - %d\n",
+ g_values [entry->dn_index], entry->dn_csn);
+
+ if (sim.verbose)
+ fprintf (sim.fout, "\n");
+
+ for (i = 0; i < sim.value_count; i++)
+ {
+ dump_value_state (&(entry->values[i]));
+ }
+
+ fprintf (sim.fout, "\n");
+}
+
+void dump_value_state (Value_State *value)
+{
+ fprintf (sim.fout, "\tvalue %s is %s\n", g_values[value->value_index],
+ value->present ? "present" : "not present");
+
+ if (sim.verbose)
+ {
+ fprintf (sim.fout, "\t\tpresent csn: %d\n", value->presense_csn);
+ fprintf (sim.fout, "\t\tdistinguished csn: %d\n", value->distinguished_csn);
+ fprintf (sim.fout, "\t\tdelete value csn: %d\n", value->delete_csn);
+ fprintf (sim.fout, "\t\tnon distinguished csns: ");
+
+ dump_list (value->non_distinguished_csns);
+
+ fprintf (sim.fout, "\n");
+ }
+}
+
+void dump_list (int *list)
+{
+ int i = 0;
+
+ while (list[i])
+ {
+ fprintf (sim.fout, "%d ", list[i]);
+ i ++;
+ }
+
+ fprintf (sim.fout, "\n");
+}
+
+/* misc functions */
+int max_val (int a, int b)
+{
+ if (a >= b)
+ return a;
+ else
+ return b;
+}
+
+int list_equal (int *list1, int *list2)
+{
+ int i;
+
+ i = 0;
+ while (list1[i] && list2[i])
+ {
+ if (list1[i] != list2[i])
+ return 0;
+
+ i ++;
+ }
+
+ if (list1[i] != list2[i])
+ return 0;
+ else
+ return 1;
+}
diff --git a/ldap/servers/plugins/replication/tests/dnp_sim2.c b/ldap/servers/plugins/replication/tests/dnp_sim2.c
new file mode 100644
index 00000000..e1838aa6
--- /dev/null
+++ b/ldap/servers/plugins/replication/tests/dnp_sim2.c
@@ -0,0 +1,972 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* dnp_simulation.c - this file varifies the correctness of dnp algorithm
+ by generating random sequences of operations, applying
+ the algorithm and outputing the result
+
+ usage: dnp_sim [-h] [-n <number of simulations> ] [-v] [-f <output file>]
+ -h - print usage information.
+ -n <number of simulations> - how many simulations to perform; default - 1.
+ -v - verbose mode (prints full entry state after each operation execution)
+ -f <output file> - file where results are stored; by default results are
+ printed to the screen.
+ -o <op file> - file that contains operation sequence to execute; by default,
+ random sequence is generated.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <memory.h>
+#include <string.h>
+#include <time.h>
+#include <windows.h>
+
+#define MAX_OPS 18 /* maximum number of operations in a simulation */
+#define MAX_VALS 10 /* maximum number of values is entry or dn */
+#define NOT_PRESENT -1
+
+/* data types */
+typedef struct value_state
+{
+ int value_index; /* value */
+ int presence_csn; /* last time at which we know the value was present */
+ int distinguished_index; /* index into dncsn list */
+ int delete_csn; /* last attempt to delete this value */
+ int present; /* flag that tells whether the value iscurrently present */
+} Value_State;
+
+typedef struct dn_csn
+{
+ int csn; /* dn csn */
+ int value_index; /* dn value */
+} Dn_Csn;
+
+typedef struct entry_state
+{
+ Dn_Csn dn_csns [MAX_VALS + 1]; /* list of dn csns for this entry */
+ int dn_csn_count; /* csn of the current dn */
+ Value_State values[MAX_VALS]; /* values of the attribute */
+ int attr_delete_csn; /* last attempt to delete the entire attribute */
+} Entry_State;
+
+typedef enum
+{
+ OP_ADD_VALUE,
+ OP_DELETE_VALUE,
+ OP_RENAME_ENTRY,
+ OP_DELETE_ATTR,
+ OP_END
+} Operation_Type;
+
+typedef struct operation
+{
+ Operation_Type type; /* operation type */
+ int csn; /* operation type */
+ int value_index; /* value to add, remove or rename from */
+}Operation;
+
+typedef struct simulator_params
+{
+ int runs; /* number of runs */
+ FILE *fout; /* output file */
+ int value_count; /* number of values */
+ int verbose; /* verbose mode */
+ Operation *ops; /* operation sequence to apply */
+ int op_count;
+}Simulator_Params;
+
+
+/* gloabl data */
+Simulator_Params sim;
+char *g_values[] =
+{
+ "v",
+ "u",
+ "w",
+ NULL
+};
+
+/* forward declarations */
+
+/* initialization */
+void process_cmd (int argc, char **argv);
+void set_default_sim_params ();
+int count_values ();
+void parse_operations_file (char *name);
+void parse_operation (char *line, int pos);
+int value2index (char *value);
+void print_usage ();
+
+/* simulation run */
+void run_simulation ();
+void generate_operations (Operation **ops, int *op_count);
+void generate_operation (Operation *op, int csn);
+int* generate_operation_order (int op_count, int seq_num);
+void apply_operation_sequence (Operation *ops, int op_count, int *order, Entry_State *entry);
+void init_entry_state (Entry_State *entry);
+void init_value_state (Value_State *val, int seq_num);
+void apply_operation (Entry_State *entry, Operation *op);
+void free_operations (Operation **ops);
+int ** new_perm_table (int op_count);
+void free_perm_table (int ***perm_table, int op_count);
+int get_perm_count (int op_count);
+void generate_perm_table (int *elements, int element_count, int static_part,
+ int **perm_table);
+void apply_add_operation (Entry_State *entry, Operation *op);
+void apply_value_delete_operation (Entry_State *entry, Operation *op);
+void apply_attr_delete_operation (Entry_State *entry, Operation *op);
+void apply_rename_operation (Entry_State *entry, Operation *op);
+void purge_value_state (Entry_State *entry, int index);
+void resolve_value_state (Entry_State *entry, int value_index);
+int value_distinguished_at_delete (Entry_State *entry, int value_index);
+int compare_entry_state (Entry_State *entry1, Entry_State *entry2, int run);
+
+/* dnc_csn handling */
+int dn_csn_add (Entry_State *entry, int value_index, int csn);
+int get_value_dn_csn (Entry_State *entry, int value_index);
+
+/* data tracing */
+void dump_operations (Operation *ops, int op_count, int *order);
+void dump_operation (Operation *op);
+void dump_perm_table (int **perm_table, int op_count);
+void dump_entry_state (Entry_State *entry);
+void dump_value_state (Value_State *value);
+void dump_dn_csn_list (Entry_State *entry);
+
+/* misc functions */
+int max_val (int a, int b);
+
+int main (int argc, char **argv)
+{
+ int i;
+
+ process_cmd (argc, argv);
+
+ for (i = 0; i < sim.runs; i++)
+ {
+ fprintf (sim.fout, "*******running simulation #%d ...\n\n", i+1);
+ run_simulation ();
+ fprintf (sim.fout, "\n*******done with simulation #%d ...\n\n", i+1);
+ }
+
+ if (sim.fout != stdout)
+ fclose (sim.fout);
+
+ return 0;
+}
+
+void process_cmd (int argc, char **argv)
+{
+ int i;
+
+ set_default_sim_params ();
+
+ if (argc == 1)
+ {
+ return;
+ }
+
+ if (strcmp (argv[1], "-h") == 0) /* print help */
+ {
+ print_usage ();
+ exit (0);
+ }
+
+ i = 1;
+ while (i < argc)
+ {
+ if (strcmp (argv[i], "-v") == 0) /* verbose mode */
+ {
+ sim.verbose = 1;
+ i ++;
+ }
+ else if (strcmp (argv[i], "-n") == 0)
+ {
+ if (i < argc - 1)
+ {
+ int runs = atoi (argv[i + 1]);
+ if (runs > 0)
+ sim.runs = runs;
+ i+=2;
+ }
+ else
+ {
+ /* ONREPL print warning */
+ i++;
+ }
+ }
+ else if (strcmp (argv[i], "-f") == 0) /* output file */
+ {
+ if (i < argc - 1)
+ {
+ FILE *f = fopen (argv[i + 1], "w");
+ if (f == 0)
+ {
+ printf ("failed to open output file; error - %s, using stdout\n",
+ strerror(errno));
+ }
+ else
+ {
+ /* ONREPL print warning */
+ sim.fout = f;
+ }
+
+ i += 2;
+ }
+ else
+ i++;
+ }
+ else if (strcmp (argv[i], "-o") == 0) /* file with operation sequence */
+ {
+ if (i < argc - 1)
+ {
+ parse_operations_file (argv[i+1]);
+ i += 2;
+ }
+ else
+ {
+ /* ONREPL print warning */
+ i ++;
+ }
+ }
+ else /* unknown option */
+ {
+ printf ("unknown option - %s; ignored\n", argv[i]);
+ i ++;
+ }
+
+ }
+}
+
+void set_default_sim_params ()
+{
+ memset (&sim, 0, sizeof (sim));
+ sim.runs = 1;
+ sim.fout = stdout;
+ sim.value_count = count_values ();
+}
+
+/* file format: <op count>
+ add <value>
+ delete <value>
+ delete attribute
+ rename to <value>
+
+ all spaces are significant
+ */
+void parse_operations_file (char *name)
+{
+ FILE *file = fopen (name, "r");
+ char line [256];
+ int i;
+
+ if (file == NULL)
+ {
+ printf ("failed to open operations file %s: error = %d\n", name, errno);
+ print_usage ();
+ exit (1);
+ }
+
+ i = 0;
+ while (fgets (line, sizeof (line), file))
+ {
+ if (i == 0)
+ {
+ /* read operation count */
+ sim.op_count = atoi (line);
+ if (sim.op_count < 1 || sim.op_count > MAX_OPS/2)
+ {
+ printf ("invalid operation count - %d; value must be between 1 and %d\n",
+ sim.op_count, MAX_OPS/2);
+ print_usage ();
+ exit (1);
+ }
+ else
+ {
+ sim.ops = (Operation*)malloc (sim.op_count * sizeof (Operation));
+ }
+ }
+ else
+ {
+ if (strlen (line) == 0) /* skip empty lines */
+ continue;
+ parse_operation (line, i);
+ }
+
+ i ++;
+ }
+}
+
+void parse_operation (char *line, int i)
+{
+ sim.ops [i - 1].csn = i;
+
+ if (line[strlen(line) - 1] == '\n')
+ line[strlen(line) - 1] = '\0';
+
+ if (strncmp (line, "add", 3) == 0)
+ {
+ sim.ops [i - 1].type = OP_ADD_VALUE;
+ sim.ops [i - 1].value_index = value2index (&line[4]);
+ }
+ else if (strncmp (line, "delete attribute", 16) == 0)
+ {
+ sim.ops [i - 1].type = OP_DELETE_ATTR;
+ }
+ else if (strncmp (line, "delete", 6) == 0)
+ {
+ sim.ops [i - 1].type = OP_DELETE_VALUE;
+ sim.ops [i - 1].value_index = value2index (&line[7]);
+ }
+ else if (strncmp (line, "rename to", 6) == 0)
+ {
+ sim.ops [i - 1].type = OP_RENAME_ENTRY;
+ sim.ops [i - 1].value_index = value2index (&line[10]);
+ }
+ else
+ {
+ /* invalid line */
+ printf ("invalid operation: %s\n", line);
+ exit (1);
+ }
+}
+
+int value2index (char *value)
+{
+ int i;
+
+ for (i = 0; i < sim.value_count; i++)
+ {
+ if (strcmp (g_values[i], value) == 0)
+ return i;
+ }
+
+ return -1;
+}
+
+void print_usage ()
+{
+ printf ("usage: dnp_sim [-h] [-n <number of simulations> ] [-v] [-f <output file>]\n"
+ "\t-h - print usage information\n"
+ "\t-n <number of simulations>; default - 1\n"
+ "\t-v - verbose mode\n"
+ "\t-f <output file> - by default, results are printed to the screen\n"
+ "\t-o <op file> - file that contains operation sequence to execute;\n"
+ "\tby default, random sequence is generated.\n");
+}
+
+int count_values ()
+{
+ int i;
+
+ for (i = 0; g_values[i]; i++);
+
+ return i;
+}
+
+void run_simulation ()
+{
+ int *order;
+ int i;
+ int perm_count;
+ Entry_State entry_first, entry_current;
+ int error = 0;
+
+ init_entry_state (&entry_first);
+ fprintf (sim.fout, "initial entry state :\n");
+ dump_entry_state (&entry_first);
+
+ if (sim.ops == NULL)
+ {
+ generate_operations (&sim.ops, &sim.op_count);
+ }
+ fprintf (sim.fout, "initial operation set:\n");
+ dump_operations (sim.ops, sim.op_count, NULL/* order */);
+
+ //DebugBreak ();
+
+ perm_count = get_perm_count (sim.op_count);
+ for (i = 0; i < perm_count; i++)
+ {
+ fprintf (sim.fout, "--------------------------------\n");
+ fprintf (sim.fout, "simulation run %d\n", i + 1);
+ fprintf (sim.fout, "--------------------------------\n");
+ order = generate_operation_order (sim.op_count, i);
+ if (i == 0)
+ apply_operation_sequence (sim.ops, sim.op_count, order, &entry_first);
+ else
+ {
+ apply_operation_sequence (sim.ops, sim.op_count, order, &entry_current);
+ error |= compare_entry_state (&entry_first, &entry_current, i + 1);
+ }
+ }
+
+ switch (error)
+ {
+ case 0: fprintf (sim.fout, "all runs left the entry in the same state\n");
+ break;
+ case 1: fprintf (sim.fout, "while value presence is consistent across all runs, "
+ "the exact state does not match\n");
+ break;
+ case 3: fprintf (sim.fout, "the runs left entries in an inconsistent state\n");
+ break;
+ }
+
+ free_operations (&sim.ops);
+}
+
+void generate_operations (Operation **ops, int *op_count)
+{
+ int i;
+
+ /* generate number operations in the sequence */
+ *op_count = slapi_rand () % (MAX_OPS / 2) + 1;
+ *ops = (Operation *)malloc (*op_count * sizeof (Operation));
+
+ for (i = 0; i < *op_count; i ++)
+ {
+ generate_operation (&((*ops)[i]), i + 1);
+ }
+}
+
+void generate_operation (Operation *op, int csn)
+{
+ /* generate operation type */
+ op->type = slapi_rand () % OP_END;
+
+ /* generate value to which operation applies */
+ op->value_index = slapi_rand () % sim.value_count;
+
+ op->csn = csn;
+}
+
+int* generate_operation_order (int op_count, int seq_num)
+{
+ static int **perm_table = NULL;
+
+ /* first request - generate pemutation table */
+ if (seq_num == 0)
+ {
+ int elements [MAX_OPS];
+ int i;
+
+ if (perm_table)
+ free_perm_table (&perm_table, op_count);
+ perm_table = new_perm_table (op_count);
+
+ for (i = 0; i < op_count; i++)
+ elements [i] = i;
+
+ generate_perm_table (elements, op_count, 0 /* static part */,
+ perm_table);
+ }
+
+ return perm_table [seq_num];
+}
+
+void apply_operation_sequence (Operation *ops, int op_count, int *order, Entry_State *entry)
+{
+ int i;
+
+ init_entry_state (entry);
+
+ if (!sim.verbose)
+ {
+ if (!sim.verbose)
+ {
+ fprintf (sim.fout, "operation_sequence for this run:\n");
+ dump_operations (ops, op_count, order);
+ }
+ }
+
+ for (i = 0; i < op_count; i++)
+ {
+ apply_operation (entry, &(ops [order[i]]));
+ }
+
+ if (!sim.verbose)
+ {
+ fprintf (sim.fout, "final entry state :\n");
+ dump_entry_state (entry);
+ }
+
+}
+
+void init_entry_state (Entry_State *entry)
+{
+ int i;
+
+ memset (entry, 0, sizeof (*entry));
+ entry->attr_delete_csn = NOT_PRESENT;
+ entry->dn_csn_count = 1;
+
+ for (i = 0; i < sim.value_count; i++)
+ {
+ init_value_state (&(entry->values[i]), i);
+ }
+}
+
+void init_value_state (Value_State *val, int seq_num)
+{
+ memset (val, 0, sizeof (*val));
+ val->value_index = seq_num;
+ val->present = 1;
+ val->delete_csn = NOT_PRESENT;
+ if (seq_num > 0) /* only first value is distinguished */
+ val->distinguished_index = -1;
+}
+
+void apply_operation (Entry_State *entry, Operation *op)
+{
+ switch (op->type)
+ {
+ case OP_ADD_VALUE: apply_add_operation (entry, op);
+ break;
+
+ case OP_DELETE_VALUE: apply_value_delete_operation (entry, op);
+ break;
+
+ case OP_DELETE_ATTR: apply_attr_delete_operation (entry, op);
+ break;
+
+ case OP_RENAME_ENTRY: apply_rename_operation (entry, op);
+ break;
+ }
+
+ if (sim.verbose)
+ {
+ fprintf (sim.fout, "operation: ");
+ dump_operation (op);
+ fprintf (sim.fout, "\n");
+ dump_entry_state (entry);
+ }
+}
+
+void free_operations (Operation **ops)
+{
+ free (*ops);
+ *ops = NULL;
+}
+
+int **new_perm_table (int op_count)
+{
+ int i;
+ int **perm_table;
+ int perm_count = get_perm_count (op_count);
+
+ perm_table = (int**)malloc (perm_count * sizeof (int*));
+ for (i = 0; i < perm_count; i ++)
+ perm_table [i] = (int*) malloc (op_count * sizeof (int));
+
+ return perm_table;
+}
+
+void free_perm_table (int ***perm_table, int op_count)
+{
+ int i;
+ int perm_count = get_perm_count (op_count);
+
+ for (i = 0; i < perm_count; i ++)
+ free ((*perm_table)[i]);
+
+ free (*perm_table);
+ *perm_table = NULL;
+}
+
+void generate_perm_table (int *elements, int element_count, int static_part,
+ int **perm_table)
+{
+ int i;
+ int elements_copy [MAX_OPS];
+ int start_pos;
+
+ if (element_count - 1 == static_part)
+ {
+ memcpy (*perm_table, elements, element_count * sizeof (int));
+ return;
+ }
+
+ start_pos = 0;
+ for (i = 0; i < element_count - static_part; i ++)
+ {
+ memcpy (elements_copy, elements, element_count * sizeof (int));
+ elements_copy [static_part] = elements [static_part + i];
+ elements_copy [static_part + i] = elements [static_part];
+ generate_perm_table (elements_copy, element_count, static_part + 1,
+ &perm_table [start_pos]);
+ start_pos += get_perm_count (element_count - static_part - 1);
+ }
+}
+
+int get_perm_count (int op_count)
+{
+ int i;
+ int perm_count = 1;
+
+ for (i = 2; i <= op_count; i ++)
+ perm_count *= i;
+
+ return perm_count;
+}
+
+void apply_add_operation (Entry_State *entry, Operation *op)
+{
+ if (entry->values[op->value_index].presence_csn < op->csn)
+ {
+ entry->values[op->value_index].presence_csn = op->csn;
+ resolve_value_state (entry, op->value_index);
+ }
+}
+
+void apply_value_delete_operation (Entry_State *entry, Operation *op)
+{
+ if (entry->values[op->value_index].delete_csn < op->csn)
+ {
+ entry->values[op->value_index].delete_csn = op->csn;
+ resolve_value_state (entry, op->value_index);
+ }
+}
+
+void apply_attr_delete_operation (Entry_State *entry, Operation *op)
+{
+ int i;
+
+ if (entry->attr_delete_csn < op->csn)
+ {
+ entry->attr_delete_csn = op->csn;
+
+ for (i = 0; i < sim.value_count; i++)
+ {
+ resolve_value_state (entry, i);
+ }
+ }
+}
+
+void apply_rename_operation (Entry_State *entry, Operation *op)
+{
+ int index;
+
+ if (entry->values[op->value_index].presence_csn == NOT_PRESENT)
+ entry->values[op->value_index].presence_csn = op->csn;
+
+ index = dn_csn_add (entry, op->value_index, op->csn);
+
+ if (index > 0)
+ resolve_value_state (entry, entry->dn_csns[index - 1].value_index);
+
+ resolve_value_state (entry, entry->dn_csns[index].value_index);
+}
+
+void purge_value_state (Entry_State *entry, int value_index)
+{
+ Value_State *value = &(entry->values[value_index]);
+ int value_distinguished_csn = get_value_dn_csn (entry, value_index);
+
+ if (value_distinguished_csn == -1 && value->presence_csn > value->delete_csn)
+ value->delete_csn = NOT_PRESENT;
+ else if (value->delete_csn < max_val (value_distinguished_csn, value->presence_csn))
+ value->delete_csn = NOT_PRESENT;
+}
+
+void resolve_value_state (Entry_State *entry, int value_index)
+{
+ Value_State *value = &(entry->values[value_index]);
+ int value_distinguished_csn = get_value_dn_csn (entry, value_index);
+
+ purge_value_state (entry, value_index);
+
+ /* no deletes that effect the state */
+ if (max_val (value->delete_csn, entry->attr_delete_csn) <
+ max_val (value_distinguished_csn, value->presence_csn))
+ {
+ value->present = 1;
+ return;
+ }
+
+ if (value->present) /* check if it should be removed based on the current state */
+ {
+ if (!value_distinguished_at_delete (entry, value_index))
+ {
+ value->present = 0;
+ }
+ }
+ else /* not present - check if it should be restored */
+ {
+ if (value_distinguished_at_delete (entry, value_index))
+ {
+ value->present = 1;
+ }
+ }
+}
+
+int value_distinguished_at_delete (Entry_State *entry, int value_index)
+{
+ Value_State *value = &(entry->values[value_index]);
+ int value_distinguished_csn = get_value_dn_csn (entry, value_index);
+ int delete_csn;
+ int i;
+
+ /* value has never been distinguished */
+ if (value_distinguished_csn == -1)
+ return 0;
+
+ delete_csn = max_val (entry->attr_delete_csn, value->delete_csn);
+
+ for (i = 0; i < entry->dn_csn_count; i++)
+ {
+ if (entry->dn_csns[i].csn > delete_csn)
+ break;
+ }
+
+ /* i is never equal to 0 because the csn of the first element is always
+ smaller than csn of any operation we can receive */
+ return (entry->dn_csns[i-1].value_index == value_index);
+}
+
+int compare_entry_state (Entry_State *entry1, Entry_State *entry2, int run)
+{
+ int i;
+ int error = 0;
+
+ /* first - quick check for present / not present */
+ for (i = 0; i < sim.value_count; i++)
+ {
+ if (entry1->values[i].present != entry2->values[i].present)
+ {
+ fprintf (sim.fout,
+ "value %s is %s present in the first run but %s present in the %d run\n",
+ g_values[i], entry1->values[i].present ? "" : "not",
+ entry2->values[i].present ? "" : "not", run);
+ error = 1;
+ }
+ }
+
+ if (error)
+ return 3;
+
+ /* compare dnc_csn list */
+ if (entry1->dn_csn_count != entry2->dn_csn_count)
+ {
+ fprintf (sim.fout, "dn_csn count is %d for run 1 and %d for run %d\n",
+ entry1->dn_csn_count, entry2->dn_csn_count, run);
+ error = 1;
+ }
+
+ for (i = 0; i < entry1->dn_csn_count; i++)
+ {
+ if (entry1->dn_csns [i].csn != entry2->dn_csns [i].csn ||
+ entry1->dn_csns [i].value_index != entry2->dn_csns [i].value_index)
+ {
+ fprintf (sim.fout,"elements %d of dn csn list are different:\n"
+ "\tfirst run: csn - %d, value - %s\n"
+ "\t%d run: csn - %d, value - %s\n", i,
+ entry1->dn_csns [i].csn,
+ g_values[entry1->dn_csns [i].value_index],
+ run, entry2->dn_csns [i].csn,
+ g_values[entry2->dn_csns [i].value_index]);
+
+ error = 1;
+ }
+ }
+
+ /* compare value state */
+ if (entry1->attr_delete_csn != entry2->attr_delete_csn)
+ {
+ fprintf (sim.fout, "attribute delete csn is %d for run 1 "
+ "but is %d for run %d\n", entry1->attr_delete_csn,
+ entry2->attr_delete_csn, run);
+ error = 1;
+ }
+
+ for (i = 0; i < sim.value_count; i++)
+ {
+ if (entry1->values[i].presence_csn != entry2->values[i].presence_csn)
+ {
+ fprintf (sim.fout, "presence csn for value %s is %d in run 1 "
+ "but is %d in run %d\n", g_values[i], entry1->values[i].presence_csn,
+ entry2->values[i].presence_csn, run);
+ error = 1;
+ }
+
+ if (entry1->values[i].distinguished_index != entry2->values[i].distinguished_index)
+ {
+ fprintf (sim.fout, "distinguished index for value %s is %d in run 1 "
+ "but is %d in run %d\n", g_values[i],
+ entry1->values[i].distinguished_index,
+ entry2->values[i].distinguished_index, run);
+ error = 1;
+ }
+
+ if (entry1->values[i].delete_csn != entry2->values[i].delete_csn)
+ {
+ fprintf (sim.fout, "delete csn for value %s is %d in run 1 "
+ "but is %d in run %d\n", g_values[i], entry1->values[i].delete_csn,
+ entry2->values[i].delete_csn, run);
+ error = 1;
+ }
+ }
+
+ if (error != 0)
+ {
+ return 1;
+ }
+ else
+ return 0;
+}
+
+int dn_csn_add (Entry_State *entry, int value_index, int csn)
+{
+ int i, j;
+ int distinguished_index;
+
+ for (i = 0; i < entry->dn_csn_count; i++)
+ {
+ if (entry->dn_csns[i].csn > csn)
+ break;
+ }
+
+ if (i < entry->dn_csn_count)
+ {
+ distinguished_index = i;
+ for (j = i; j < entry->dn_csn_count; j ++)
+ {
+ if (entry->dn_csns[j].value_index == value_index)
+ distinguished_index = j + 1;
+
+ if (entry->values [entry->dn_csns[j].value_index].distinguished_index == j)
+ entry->values [entry->dn_csns[j].value_index].distinguished_index ++;
+ }
+
+ memcpy (&(entry->dn_csns[i+1]), &(entry->dn_csns[i]),
+ (entry->dn_csn_count - i) * sizeof (Dn_Csn));
+ }
+ else
+ {
+ distinguished_index = entry->dn_csn_count;
+ }
+
+ entry->values[value_index].distinguished_index = distinguished_index;
+ entry->dn_csns[i].csn = csn;
+ entry->dn_csns[i].value_index = value_index;
+ entry->dn_csn_count ++;
+
+ return i;
+}
+
+int get_value_dn_csn (Entry_State *entry, int value_index)
+{
+ Value_State *value = &(entry->values [value_index]);
+
+ if (value->distinguished_index == -1)
+ return -1;
+ else
+ return entry->dn_csns [value->distinguished_index].csn;
+}
+
+void dump_operations (Operation *ops, int op_count, int *order)
+{
+ int index;
+ int i;
+
+ for (i = 0; i < op_count; i ++)
+ {
+ if (order == NULL) /* current order */
+ index = i;
+ else
+ index = order [i];
+
+ dump_operation (&ops[index]);
+ }
+
+ fprintf (sim.fout, "\n");
+}
+
+void dump_operation (Operation *op)
+{
+ switch (op->type)
+ {
+ case OP_ADD_VALUE:
+ fprintf (sim.fout, "\t%d add value %s\n", op->csn,
+ g_values [op->value_index]);
+ break;
+ case OP_DELETE_VALUE:
+ fprintf (sim.fout, "\t%d delete value %s\n", op->csn,
+ g_values [op->value_index]);
+ break;
+ case OP_DELETE_ATTR:
+ fprintf (sim.fout, "\t%d delete attribute\n", op->csn);
+ break;
+ case OP_RENAME_ENTRY:
+ fprintf (sim.fout, "\t%d rename entry to %s\n", op->csn,
+ g_values [op->value_index]);
+ break;
+ }
+}
+
+void dump_perm_table (int **perm_table, int op_count)
+{
+ int i, j;
+ int perm_count = get_perm_count (op_count);
+
+ for (i = 0; i < op_count; i++)
+ {
+ for (j = 0; j < perm_count; j++)
+ {
+ fprintf (sim.fout, "%d ", perm_table [j][i]);
+ }
+
+ fprintf (sim.fout, "\n");
+ }
+}
+
+void dump_entry_state (Entry_State *entry)
+{
+ int i;
+
+ dump_dn_csn_list (entry);
+
+ for (i = 0; i < sim.value_count; i++)
+ {
+ dump_value_state (&(entry->values[i]));
+ }
+
+ fprintf (sim.fout, "\n");
+}
+
+void dump_value_state (Value_State *value)
+{
+ fprintf (sim.fout, "\tvalue %s is %s\n", g_values[value->value_index],
+ value->present ? "present" : "not present");
+
+ if (sim.verbose)
+ {
+ fprintf (sim.fout, "\t\tpresent csn: %d\n", value->presence_csn);
+ fprintf (sim.fout, "\t\tdistinguished index: %d\n", value->distinguished_index);
+ fprintf (sim.fout, "\t\tdelete value csn: %d\n", value->delete_csn);
+ }
+}
+
+void dump_dn_csn_list (Entry_State *entry)
+{
+ int i;
+
+ fprintf (sim.fout, "\tdn csn list: \n");
+ for (i = 0; i < entry->dn_csn_count; i++)
+ {
+ fprintf (sim.fout, "\t\tvalue: %s, csn: %d\n",
+ g_values[entry->dn_csns[i].value_index], entry->dn_csns[i].csn);
+ }
+}
+
+/* misc functions */
+int max_val (int a, int b)
+{
+ if (a >= b)
+ return a;
+ else
+ return b;
+}
diff --git a/ldap/servers/plugins/replication/tests/dnp_sim3.c b/ldap/servers/plugins/replication/tests/dnp_sim3.c
new file mode 100644
index 00000000..d018597a
--- /dev/null
+++ b/ldap/servers/plugins/replication/tests/dnp_sim3.c
@@ -0,0 +1,1489 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* dnp_simulation.c - this file varifies the correctness of dnp algorithm
+ by generating random sequences of operations, applying
+ the algorithm and outputing the result
+
+ usage: dnp_sim [-h] [-n <number of simulations> ] [-v] [-f <output file>]
+ -h - print usage information.
+ -n <number of simulations> - how many simulations to perform; default - 1.
+ -v - verbose mode (prints full entry state after each operation execution)
+ -f <output file> - file where results are stored; by default results are
+ printed to the screen.
+ -o <op file> - file that contains operation sequence to execute; by default,
+ random sequence is generated.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <memory.h>
+#include <string.h>
+#include <time.h>
+#include <windows.h>
+
+#define MAX_OPS 18 /* maximum number of operations in a simulation */
+#define MAX_VALS 10 /* maximum number of values is entry or dn */
+#define MAX_ATTR_NAME 16 /* max length of the attribute name */
+#define NOT_PRESENT -1
+#define SV_ATTR_NAME "sv_attr" /* name of the singlevalued attribute */
+#define MV_ATTR_NAME "mv_attr" /* name of the multivalued attribute */
+
+/* data types */
+
+/* value */
+typedef struct value_state
+{
+ int value_index; /* value */
+ int presence_csn; /* last time at which we know the value was present */
+ int delete_csn; /* last attempt to delete this value */
+ int present; /* flag that tells whether the value is present */
+} Value_State;
+
+/* shared attribute state */
+typedef struct attr_state
+{
+ int delete_csn; /* last deletion csn */
+ int present; /* flag that tells whether the attribute is present */
+}Attr_State;
+
+/* singlevalued attribute */
+typedef struct sv_attr_state
+{
+ Attr_State attr_state; /* shared attribute state */
+ Value_State current_value; /* current attribute value */
+ Value_State *pending_value; /* latest pending value */
+} SV_Attr_State;
+
+/* maltivalued attribute */
+typedef struct mv_attr_state
+{
+ Attr_State attr_state; /* shared attribute state */
+ Value_State values [MAX_VALS]; /* latest pending value */
+ int value_count; /* number of values in the array */
+} MV_Attr_State;
+
+/* node of dn_csn_list */
+typedef struct dn_csn
+{
+ int csn; /* dn csn */
+ int sv_attr; /* is this single valued or multivalued attr */
+ int value_index; /* dn value */
+} Dn_Csn;
+
+typedef struct entry_state
+{
+ Dn_Csn dn_csns [MAX_VALS + 1]; /* list of dn csns for this entry */
+ int dn_csn_count; /* csn of the current dn */
+ SV_Attr_State sv_attr; /* singlevalued attribute */
+ MV_Attr_State mv_attr; /* singlevalued attribute */
+} Entry_State;
+
+typedef enum
+{
+ OP_ADD_VALUE,
+ OP_DELETE_VALUE,
+ OP_RENAME_ENTRY,
+ OP_DELETE_ATTR,
+ OP_END
+} Operation_Type;
+
+typedef struct operation
+{
+ Operation_Type type; /* operation type */
+ int csn; /* operation csn */
+ int sv_attr; /* is this applied to singlevalued attribute */
+ int value_index; /* value to add, remove or rename from */
+ int delete_old_rdn; /* rename only */
+ int old_rdn_sv_attr; /* is oldrdn a singlevalued attribute */
+ int old_rdn_value_index; /* index of old_rdn */
+}Operation;
+
+typedef struct simulator_params
+{
+ int runs; /* number of runs */
+ FILE *fout; /* output file */
+ int value_count; /* number of values */
+ int verbose; /* verbose mode */
+ Operation *ops; /* operation sequence to apply */
+ int op_count;
+}Simulator_Params;
+
+
+/* gloabl data */
+Simulator_Params sim;
+char *g_values[] =
+{
+ "v",
+ "u",
+ "w",
+ NULL
+};
+
+/* forward declarations */
+
+/* initialization */
+void process_cmd (int argc, char **argv);
+void set_default_sim_params ();
+int count_values ();
+void parse_operations_file (char *name);
+void parse_operation (char *line, int pos);
+int value2index (char *value);
+void print_usage ();
+
+/* simulation run */
+void run_simulation ();
+void generate_operations (Operation **ops, int *op_count);
+void generate_operation (Operation *op, int csn);
+int* generate_operation_order (int op_count, int seq_num);
+void apply_operation_sequence (Operation *ops, int op_count, int *order, Entry_State *entry);
+void init_entry_state (Entry_State *entry);
+void init_sv_attr_state (SV_Attr_State *sv_attr);
+void init_mv_attr_state (MV_Attr_State *mv_attr);
+void init_value_state (Value_State *val, int seq_num);
+void free_operations (Operation **ops);
+int ** new_perm_table (int op_count);
+void free_perm_table (int ***perm_table, int op_count);
+int get_perm_count (int op_count);
+void generate_perm_table (int *elements, int element_count, int static_part,
+ int **perm_table);
+void apply_operation (Entry_State *entry, Operation *op);
+void apply_add_operation (Entry_State *entry, Operation *op);
+void apply_value_delete_operation (Entry_State *entry, Operation *op);
+void apply_attr_delete_operation (Entry_State *entry, Operation *op);
+void apply_rename_operation (Entry_State *entry, Operation *op);
+void resolve_mv_attr_state (Entry_State *entry, Value_State *value);
+void resolve_sv_attr_state (Entry_State *entry, Value_State *value);
+void purge_sv_attr_state (Entry_State *entry);
+void purge_mv_attr_state (Entry_State *entry, Value_State *value);
+int value_distinguished_at_csn (Entry_State *entry, int sv_attr, Value_State *value, int csn);
+
+/* state comparison */
+int compare_entry_state (Entry_State *entry1, Entry_State *entry2, int run);
+int compare_entry_state_quick (Entry_State *entry1, Entry_State *entry2, int run);
+int compare_sv_attr_state_quick (SV_Attr_State *sv_attr1, SV_Attr_State *sv_attr2, int run);
+int compare_mv_attr_state_quick (MV_Attr_State *mv_attr1, MV_Attr_State *mv_attr2, int run);
+int compare_sv_attr_state (SV_Attr_State *sv_attr1, SV_Attr_State *sv_attr2, int run);
+int compare_mv_attr_state (MV_Attr_State *mv_attr1, MV_Attr_State *mv_attr2, int run);
+int compare_value_state (Value_State *value1, Value_State *value2, int run);
+
+/* dnc_csn handling */
+int dn_csn_add (Entry_State *entry, int sv_attr, int value_index, int csn);
+
+/* data tracing */
+void dump_operations (Operation *ops, int op_count, int *order);
+void dump_operation (Operation *op);
+void dump_perm_table (int **perm_table, int op_count);
+void dump_entry_state (Entry_State *entry);
+void dump_sv_attr_state (SV_Attr_State *sv_attr);
+void dump_mv_attr_state (MV_Attr_State *mv_attr);
+void dump_value_state (Value_State *value, int sv_attr);
+void dump_dn_csn_list (Entry_State *entry);
+
+/* misc functions */
+int max_val (int a, int b);
+
+int main (int argc, char **argv)
+{
+ int i;
+
+ process_cmd (argc, argv);
+
+ for (i = 0; i < sim.runs; i++)
+ {
+ fprintf (sim.fout, "*******running simulation #%d ...\n\n", i+1);
+ run_simulation ();
+ fprintf (sim.fout, "\n*******done with simulation #%d ...\n\n", i+1);
+ }
+
+ if (sim.fout != stdout)
+ fclose (sim.fout);
+
+ return 0;
+}
+
+void process_cmd (int argc, char **argv)
+{
+ int i;
+
+ set_default_sim_params ();
+
+ if (argc == 1)
+ {
+ return;
+ }
+
+ if (strcmp (argv[1], "-h") == 0) /* print help */
+ {
+ print_usage ();
+ exit (0);
+ }
+
+ i = 1;
+ while (i < argc)
+ {
+ if (strcmp (argv[i], "-v") == 0) /* verbose mode */
+ {
+ sim.verbose = 1;
+ i ++;
+ }
+ else if (strcmp (argv[i], "-n") == 0)
+ {
+ if (i < argc - 1)
+ {
+ int runs = atoi (argv[i + 1]);
+ if (runs > 0)
+ sim.runs = runs;
+ i+=2;
+ }
+ else
+ {
+ /* ONREPL print warning */
+ i++;
+ }
+ }
+ else if (strcmp (argv[i], "-f") == 0) /* output file */
+ {
+ if (i < argc - 1)
+ {
+ FILE *f = fopen (argv[i + 1], "w");
+ if (f == 0)
+ {
+ printf ("failed to open output file; error - %s, using stdout\n",
+ strerror(errno));
+ }
+ else
+ {
+ /* ONREPL print warning */
+ sim.fout = f;
+ }
+
+ i += 2;
+ }
+ else
+ i++;
+ }
+ else if (strcmp (argv[i], "-o") == 0) /* file with operation sequence */
+ {
+ if (i < argc - 1)
+ {
+ parse_operations_file (argv[i+1]);
+ i += 2;
+ }
+ else
+ {
+ /* ONREPL print warning */
+ i ++;
+ }
+ }
+ else /* unknown option */
+ {
+ printf ("unknown option - %s; ignored\n", argv[i]);
+ i ++;
+ }
+
+ }
+}
+
+void set_default_sim_params ()
+{
+ memset (&sim, 0, sizeof (sim));
+ sim.runs = 1;
+ sim.fout = stdout;
+ sim.value_count = count_values ();
+}
+
+/* file format: <operation count>
+ add <attribute> <value>
+ delete <attribute>[ <value>]
+ rename to <attribute> <value>[ delete <attribute> <value>]
+
+ all spaces are significant
+ */
+void parse_operations_file (char *name)
+{
+ FILE *file = fopen (name, "r");
+ char line [256];
+ int i;
+
+ if (file == NULL)
+ {
+ printf ("failed to open operations file %s: error = %d\n", name, errno);
+ print_usage ();
+ exit (1);
+ }
+
+ i = 0;
+ while (fgets (line, sizeof (line), file))
+ {
+ if (i == 0)
+ {
+ /* read operation count */
+ sim.op_count = atoi (line);
+ if (sim.op_count < 1 || sim.op_count > MAX_OPS/2)
+ {
+ printf ("invalid operation count - %d; value must be between 1 and %d\n",
+ sim.op_count, MAX_OPS/2);
+ print_usage ();
+ exit (1);
+ }
+ else
+ {
+ sim.ops = (Operation*)malloc (sim.op_count * sizeof (Operation));
+ }
+ }
+ else
+ {
+ if (strlen (line) == 0) /* skip empty lines */
+ continue;
+ parse_operation (line, i);
+ }
+
+ i ++;
+ }
+}
+
+#define ADD_KEYWORD "add "
+#define DELETE_KEYWORD "delete "
+#define RENAME_KEYWORD "rename to "
+#define DELET_OLD_RDN_KEYWORD " delete "
+
+void parse_operation (char *line, int i)
+{
+ int rc = 0;
+ char *pos;
+ char buff [64];
+
+ sim.ops [i - 1].csn = i;
+
+ if (line[strlen(line) - 1] == '\n')
+ line[strlen(line) - 1] = '\0';
+
+ /* add <attribute> <value> */
+ if (strncmp (line, ADD_KEYWORD, strlen (ADD_KEYWORD)) == 0)
+ {
+ sim.ops [i - 1].type = OP_ADD_VALUE;
+ pos = strchr (&line[strlen (ADD_KEYWORD)], ' ');
+ if (pos == NULL)
+ {
+ rc = -1;
+ goto done;
+ }
+
+ memset (buff, 0, sizeof (buff));
+ strncpy (buff, &line[strlen (ADD_KEYWORD)], pos - &line[strlen (ADD_KEYWORD)]);
+ sim.ops [i - 1].sv_attr = strcmp (buff, MV_ATTR_NAME);
+ sim.ops [i - 1].value_index = value2index (pos + 1);
+ }
+ /* delete <attribute>[ <value>] */
+ else if (strncmp (line, DELETE_KEYWORD, strlen (DELETE_KEYWORD)) == 0)
+ {
+ pos = strchr (&line[strlen (DELETE_KEYWORD)], ' ');
+ if (pos == NULL) /* delete attribute version */
+ {
+ sim.ops [i - 1].type = OP_DELETE_ATTR;
+ sim.ops [i - 1].sv_attr = strcmp (&line[strlen (DELETE_KEYWORD)],
+ MV_ATTR_NAME);
+ }
+ else /* delete value version */
+ {
+ memset (buff, 0, sizeof (buff));
+ sim.ops [i - 1].type = OP_DELETE_VALUE;
+ strncpy (buff, &line[strlen (DELETE_KEYWORD)],
+ pos - &line[strlen (DELETE_KEYWORD)]);
+ sim.ops [i - 1].sv_attr = strcmp (buff, MV_ATTR_NAME);
+ sim.ops [i - 1].value_index = value2index (pos + 1);
+ }
+ }
+ /* rename to <attribute> <valued>[ delete <attribute> <value>] */
+ else if (strncmp (line, RENAME_KEYWORD, 10) == 0)
+ {
+ char *pos2;
+
+ sim.ops [i - 1].type = OP_RENAME_ENTRY;
+
+ pos = strchr (&line[strlen (RENAME_KEYWORD)], ' ');
+ if (pos == NULL)
+ {
+ rc = -1;
+ goto done;
+ }
+
+ memset (buff, 0, sizeof (buff));
+ strncpy (buff, &line[strlen (RENAME_KEYWORD)], pos - &line[strlen (RENAME_KEYWORD)]);
+ sim.ops [i - 1].sv_attr = strcmp (buff, MV_ATTR_NAME);
+
+ pos2 = strstr (pos + 1, DELET_OLD_RDN_KEYWORD);
+ if (pos2 == NULL) /* no delete old rdn part */
+ {
+ sim.ops [i - 1].value_index = value2index (pos + 1);
+ sim.ops [i - 1].delete_old_rdn = 0;
+ }
+ else
+ {
+ memset (buff, 0, sizeof (buff));
+ strncpy (buff, pos + 1, pos2 - pos - 1);
+ sim.ops [i - 1].value_index = value2index (buff);
+ pos2 += strlen (DELET_OLD_RDN_KEYWORD);
+ pos = strchr (pos2, ' ');
+ if (pos == NULL)
+ {
+ rc = -1;
+ goto done;
+ }
+
+ memset (buff, 0, sizeof (buff));
+ strncpy (buff, pos2, pos - pos2);
+ sim.ops [i - 1].delete_old_rdn = 1;
+ sim.ops [i - 1].old_rdn_sv_attr = strcmp (buff, MV_ATTR_NAME);
+ sim.ops [i - 1].old_rdn_value_index = value2index (pos + 1);
+ }
+ }
+ else
+ {
+ /* error */
+ rc = -1;
+ }
+
+done:
+ if (rc)
+ {
+ /* invalid line */
+ printf ("invalid operation: %s\n", line);
+ exit (1);
+ }
+}
+int value2index (char *value)
+{
+ int i;
+
+ for (i = 0; i < sim.value_count; i++)
+ {
+ if (strcmp (g_values[i], value) == 0)
+ return i;
+ }
+
+ return -1;
+}
+
+void print_usage ()
+{
+ printf ("usage: dnp_sim [-h] [-n <number of simulations> ] [-v] [-f <output file>]\n"
+ "\t-h - print usage information\n"
+ "\t-n <number of simulations>; default - 1\n"
+ "\t-v - verbose mode\n"
+ "\t-f <output file> - by default, results are printed to the screen\n"
+ "\t-o <op file> - file that contains operation sequence to execute;\n"
+ "\tby default, random sequence is generated.\n");
+}
+
+int count_values ()
+{
+ int i;
+
+ for (i = 0; g_values[i]; i++);
+
+ return i;
+}
+
+void run_simulation ()
+{
+ int *order;
+ int i;
+ int perm_count;
+ Entry_State entry_first, entry_current;
+ int error = 0;
+
+ init_entry_state (&entry_first);
+ fprintf (sim.fout, "initial entry state :\n");
+ dump_entry_state (&entry_first);
+
+ if (sim.ops == NULL)
+ {
+ generate_operations (&sim.ops, &sim.op_count);
+ }
+ fprintf (sim.fout, "initial operation set:\n");
+ dump_operations (sim.ops, sim.op_count, NULL/* order */);
+
+ perm_count = get_perm_count (sim.op_count);
+ for (i = 0; i < perm_count; i++)
+ {
+ fprintf (sim.fout, "--------------------------------\n");
+ fprintf (sim.fout, "simulation run %d\n", i + 1);
+ fprintf (sim.fout, "--------------------------------\n");
+ order = generate_operation_order (sim.op_count, i);
+ if (i == 0)
+ apply_operation_sequence (sim.ops, sim.op_count, order, &entry_first);
+ else
+ {
+ apply_operation_sequence (sim.ops, sim.op_count, order, &entry_current);
+ error |= compare_entry_state (&entry_first, &entry_current, i + 1);
+ }
+ }
+
+ switch (error)
+ {
+ case 0: fprintf (sim.fout, "all runs left the entry in the same state\n");
+ break;
+ case 1: fprintf (sim.fout, "while value presence is consistent across all runs, "
+ "the exact state does not match\n");
+ break;
+ case 3: fprintf (sim.fout, "the runs left entries in an inconsistent state\n");
+ break;
+ }
+
+ free_operations (&sim.ops);
+}
+
+void generate_operations (Operation **ops, int *op_count)
+{
+ int i;
+
+ /* generate number operations in the sequence */
+ *op_count = slapi_rand () % (MAX_OPS / 2) + 1;
+ *ops = (Operation *)malloc (*op_count * sizeof (Operation));
+
+ for (i = 0; i < *op_count; i ++)
+ {
+ generate_operation (&((*ops)[i]), i + 1);
+ }
+}
+
+void generate_operation (Operation *op, int csn)
+{
+ /* generate operation type */
+ op->type = slapi_rand () % OP_END;
+ op->csn = csn;
+
+ /* choose if the operation applies to the single value or
+ the multivalued attribute */
+ op->sv_attr = slapi_rand () % 2;
+
+ /* generate value to which operation applies */
+ op->value_index = slapi_rand () % sim.value_count;
+
+ if (op->type == OP_RENAME_ENTRY)
+ {
+ op->delete_old_rdn = slapi_rand () % 2;
+ if (op->delete_old_rdn)
+ {
+ op->old_rdn_sv_attr = slapi_rand () % 2;
+ op->old_rdn_value_index = slapi_rand () % sim.value_count;
+
+ while (op->old_rdn_sv_attr == op->sv_attr &&
+ op->old_rdn_value_index == op->value_index)
+ {
+ op->old_rdn_sv_attr = slapi_rand () % 2;
+ op->old_rdn_value_index = slapi_rand () % sim.value_count;
+ }
+ }
+ }
+}
+
+int* generate_operation_order (int op_count, int seq_num)
+{
+ static int **perm_table = NULL;
+
+ /* first request - generate pemutation table */
+ if (seq_num == 0)
+ {
+ int elements [MAX_OPS];
+ int i;
+
+ if (perm_table)
+ free_perm_table (&perm_table, op_count);
+ perm_table = new_perm_table (op_count);
+
+ for (i = 0; i < op_count; i++)
+ elements [i] = i;
+
+ generate_perm_table (elements, op_count, 0 /* static part */,
+ perm_table);
+ }
+
+ return perm_table [seq_num];
+}
+
+void apply_operation_sequence (Operation *ops, int op_count, int *order, Entry_State *entry)
+{
+ int i;
+
+ init_entry_state (entry);
+
+ if (!sim.verbose)
+ {
+ if (!sim.verbose)
+ {
+ fprintf (sim.fout, "operation_sequence for this run:\n");
+ dump_operations (ops, op_count, order);
+ }
+ }
+
+ for (i = 0; i < op_count; i++)
+ {
+ apply_operation (entry, &(ops [order[i]]));
+ }
+
+ if (!sim.verbose)
+ {
+ fprintf (sim.fout, "final entry state :\n");
+ dump_entry_state (entry);
+ }
+}
+
+void init_entry_state (Entry_State *entry)
+{
+ memset (entry, 0, sizeof (*entry));
+ entry->dn_csn_count = 1;
+
+ init_sv_attr_state (&entry->sv_attr);
+ init_mv_attr_state (&entry->mv_attr);
+}
+
+void init_sv_attr_state (SV_Attr_State *sv_attr)
+{
+ memset (sv_attr, 0, sizeof (*sv_attr));
+ sv_attr->attr_state.delete_csn = NOT_PRESENT;
+ sv_attr->attr_state.present = 1;
+ init_value_state (&sv_attr->current_value, 1);
+}
+
+void init_mv_attr_state (MV_Attr_State *mv_attr)
+{
+ int i;
+
+ memset (mv_attr, 0, sizeof (*mv_attr));
+ mv_attr->attr_state.delete_csn = NOT_PRESENT;
+ mv_attr->attr_state.present = 1;
+ mv_attr->value_count = sim.value_count;
+
+ for (i = 0; i < mv_attr->value_count; i++)
+ {
+ init_value_state (&(mv_attr->values[i]), i);
+ }
+}
+
+void init_value_state (Value_State *val, int seq_num)
+{
+ memset (val, 0, sizeof (*val));
+ val->value_index = seq_num;
+ val->present = 1;
+ val->delete_csn = NOT_PRESENT;
+}
+
+void apply_operation (Entry_State *entry, Operation *op)
+{
+ switch (op->type)
+ {
+ case OP_ADD_VALUE: apply_add_operation (entry, op);
+ break;
+
+ case OP_DELETE_VALUE: apply_value_delete_operation (entry, op);
+ break;
+
+ case OP_DELETE_ATTR: apply_attr_delete_operation (entry, op);
+ break;
+
+ case OP_RENAME_ENTRY: apply_rename_operation (entry, op);
+ break;
+ }
+
+ if (sim.verbose)
+ {
+ fprintf (sim.fout, "operation: ");
+ dump_operation (op);
+ fprintf (sim.fout, "\n");
+ dump_entry_state (entry);
+ }
+}
+
+void free_operations (Operation **ops)
+{
+ free (*ops);
+ *ops = NULL;
+}
+
+int **new_perm_table (int op_count)
+{
+ int i;
+ int **perm_table;
+ int perm_count = get_perm_count (op_count);
+
+ perm_table = (int**)malloc (perm_count * sizeof (int*));
+ for (i = 0; i < perm_count; i ++)
+ perm_table [i] = (int*) malloc (op_count * sizeof (int));
+
+ return perm_table;
+}
+
+void free_perm_table (int ***perm_table, int op_count)
+{
+ int i;
+ int perm_count = get_perm_count (op_count);
+
+ for (i = 0; i < perm_count; i ++)
+ free ((*perm_table)[i]);
+
+ free (*perm_table);
+ *perm_table = NULL;
+}
+
+void generate_perm_table (int *elements, int element_count, int static_part,
+ int **perm_table)
+{
+ int i;
+ int elements_copy [MAX_OPS];
+ int start_pos;
+
+ if (element_count - 1 == static_part)
+ {
+ memcpy (*perm_table, elements, element_count * sizeof (int));
+ return;
+ }
+
+ start_pos = 0;
+ for (i = 0; i < element_count - static_part; i ++)
+ {
+ memcpy (elements_copy, elements, element_count * sizeof (int));
+ elements_copy [static_part] = elements [static_part + i];
+ elements_copy [static_part + i] = elements [static_part];
+ generate_perm_table (elements_copy, element_count, static_part + 1,
+ &perm_table [start_pos]);
+ start_pos += get_perm_count (element_count - static_part - 1);
+ }
+}
+
+int get_perm_count (int op_count)
+{
+ int i;
+ int perm_count = 1;
+
+ for (i = 2; i <= op_count; i ++)
+ perm_count *= i;
+
+ return perm_count;
+}
+
+void apply_add_operation (Entry_State *entry, Operation *op)
+{
+ if (op->sv_attr)
+ {
+ Value_State *val;
+ Value_State temp_val;
+
+ if (op->value_index == entry->sv_attr.current_value.value_index)
+ {
+ val = &entry->sv_attr.current_value;
+ }
+ else if (entry->sv_attr.pending_value &&
+ op->value_index == entry->sv_attr.pending_value->value_index)
+ {
+ val = entry->sv_attr.pending_value;
+ }
+ else /* new value */
+ {
+ init_value_state (&temp_val, op->value_index);
+ val = &temp_val;
+ }
+
+ if (val->presence_csn < op->csn)
+ val->presence_csn = op->csn;
+
+ resolve_sv_attr_state (entry, val);
+ }
+ else
+ {
+ if (entry->mv_attr.values[op->value_index].presence_csn < op->csn)
+ {
+ entry->mv_attr.values[op->value_index].presence_csn = op->csn;
+ resolve_mv_attr_state (entry, &(entry->mv_attr.values[op->value_index]));
+ }
+ }
+}
+
+void apply_value_delete_operation (Entry_State *entry, Operation *op)
+{
+ if (op->sv_attr)
+ {
+ if (entry->sv_attr.attr_state.delete_csn < op->csn)
+ {
+ entry->sv_attr.attr_state.delete_csn = op->csn;
+ resolve_sv_attr_state (entry, NULL);
+ }
+ }
+ else /* mv attr */
+ {
+ if (entry->mv_attr.values[op->value_index].delete_csn < op->csn)
+ {
+ entry->mv_attr.values[op->value_index].delete_csn = op->csn;
+ resolve_mv_attr_state (entry, &(entry->mv_attr.values[op->value_index]));
+ }
+ }
+}
+
+void apply_attr_delete_operation (Entry_State *entry, Operation *op)
+{
+ int i;
+
+ if (op->sv_attr)
+ {
+ if (entry->sv_attr.attr_state.delete_csn < op->csn)
+ {
+ entry->sv_attr.attr_state.delete_csn = op->csn;
+ resolve_sv_attr_state (entry, NULL);
+ }
+ }
+ else /* mv attr */
+ {
+ if (entry->mv_attr.attr_state.delete_csn < op->csn)
+ {
+ entry->mv_attr.attr_state.delete_csn = op->csn;
+
+ for (i = 0; i < sim.value_count; i++)
+ {
+ resolve_mv_attr_state (entry, &(entry->mv_attr.values[i]));
+ }
+ }
+ }
+}
+
+void apply_rename_operation (Entry_State *entry, Operation *op)
+{
+ int index;
+ Operation del_op;
+
+ /* insert new dn into dn_csn_list */
+ index = dn_csn_add (entry, op->sv_attr, op->value_index, op->csn);
+
+ /* issue delete value operation for the old rdn */
+ if (op->delete_old_rdn)
+ {
+ del_op.type = OP_DELETE_VALUE;
+ del_op.csn = op->csn;
+ del_op.sv_attr = op->old_rdn_sv_attr;
+ del_op.value_index = op->old_rdn_value_index;
+
+ apply_value_delete_operation (entry, &del_op);
+ }
+
+ /* resolve state of the previous node in dn_csn_list */
+ if (index > 0)
+ {
+ if (entry->dn_csns[index-1].sv_attr)
+ {
+ if (entry->dn_csns[index-1].value_index ==
+ entry->sv_attr.current_value.value_index)
+ {
+ resolve_sv_attr_state (entry, &(entry->sv_attr.current_value));
+ }
+ else if (entry->sv_attr.pending_value &&
+ entry->dn_csns[index-1].value_index ==
+ entry->sv_attr.pending_value->value_index)
+ {
+ resolve_sv_attr_state (entry, entry->sv_attr.pending_value);
+ }
+ }
+ else
+ {
+ int i = entry->dn_csns[index-1].value_index;
+ resolve_mv_attr_state (entry, &(entry->mv_attr.values[i]));
+ }
+ }
+
+ /* resolve state of the new dn */
+ if (op->sv_attr)
+ {
+ Value_State *value;
+ Value_State temp_val;
+ if (op->value_index == entry->sv_attr.current_value.value_index)
+ {
+ value = &entry->sv_attr.current_value;
+ }
+ else if (entry->sv_attr.pending_value &&
+ op->value_index == entry->sv_attr.pending_value->value_index)
+ {
+ value = entry->sv_attr.pending_value;
+ }
+ else /* new value */
+ {
+ init_value_state (&temp_val, op->value_index);
+ value = &temp_val;
+ }
+
+ if (value->presence_csn == NOT_PRESENT || value->presence_csn < op->csn)
+ value->presence_csn = op->csn;
+ resolve_sv_attr_state (entry, value);
+ }
+ else
+ {
+ if (entry->mv_attr.values[op->value_index].presence_csn == NOT_PRESENT ||
+ entry->mv_attr.values[op->value_index].presence_csn < op->csn)
+ entry->mv_attr.values[op->value_index].presence_csn = op->csn;
+
+ resolve_mv_attr_state (entry, &(entry->mv_attr.values[op->value_index]));
+ }
+}
+
+void purge_mv_attr_state (Entry_State *entry, Value_State *value)
+{
+ if (value->presence_csn > value->delete_csn)
+ value->delete_csn = NOT_PRESENT;
+}
+
+void purge_sv_attr_state (Entry_State *entry)
+{
+ if (entry->sv_attr.attr_state.delete_csn != NOT_PRESENT)
+ {
+ if (entry->sv_attr.pending_value)
+ {
+ if (entry->sv_attr.attr_state.delete_csn <
+ entry->sv_attr.pending_value->presence_csn)
+ {
+ entry->sv_attr.attr_state.delete_csn = NOT_PRESENT;
+ }
+ }
+ else
+ {
+ if (entry->sv_attr.attr_state.delete_csn <
+ entry->sv_attr.current_value.presence_csn)
+ entry->sv_attr.attr_state.delete_csn = NOT_PRESENT;
+ }
+ }
+}
+
+void resolve_mv_attr_state (Entry_State *entry, Value_State *value)
+{
+ purge_mv_attr_state (entry, value);
+
+ /* no deletes that effect the state */
+ if (max_val (value->delete_csn, entry->mv_attr.attr_state.delete_csn) <
+ value->presence_csn)
+ {
+ value->present = 1;
+ return;
+ }
+
+ if (value->present) /* check if it should be removed based on the current state */
+ {
+ if (!value_distinguished_at_csn (entry, 0, value,
+ max (value->delete_csn, entry->mv_attr.attr_state.delete_csn)))
+ {
+ value->present = 0;
+ }
+ }
+ else /* not present - check if it should be restored */
+ {
+ if (value_distinguished_at_csn (entry, 0, value,
+ max (value->delete_csn, entry->mv_attr.attr_state.delete_csn)))
+ {
+ value->present = 1;
+ }
+ }
+
+ if (entry->mv_attr.attr_state.delete_csn == NOT_PRESENT)
+ {
+ entry->mv_attr.attr_state.present = 1;
+ }
+ else
+ {
+ int i;
+ int distinguished = 0;
+
+ for (i = 0; i < entry->mv_attr.value_count; i ++)
+ {
+ distinguished |= value_distinguished_at_csn (entry, 0,
+ &(entry->mv_attr.values[i]),
+ entry->mv_attr.attr_state.delete_csn);
+ }
+
+ entry->mv_attr.attr_state.present = distinguished;
+ }
+}
+
+void resolve_sv_attr_state (Entry_State *entry, Value_State *value)
+{
+ purge_sv_attr_state (entry);
+
+ if (value)
+ {
+ /* existing value is modified */
+ if (value == &(entry->sv_attr.current_value) ||
+ value == entry->sv_attr.pending_value)
+ {
+ /* check if current value should be replaced with the pending value */
+ if (entry->sv_attr.pending_value)
+ {
+ if (!value_distinguished_at_csn (entry, 1, &entry->sv_attr.current_value,
+ entry->sv_attr.current_value.presence_csn))
+ {
+ /* replace current value with the pending value */
+ memcpy (&entry->sv_attr.current_value, entry->sv_attr.pending_value,
+ sizeof (Value_State));
+ free (entry->sv_attr.pending_value);
+ entry->sv_attr.pending_value = NULL;
+ }
+ }
+ }
+ else /* addition of a new value */
+ {
+ /* new value is before the current value; note that, for new value,
+ presence_csn is the same as distinguished_csn */
+ if (value->presence_csn < entry->sv_attr.current_value.presence_csn)
+ {
+ /* if new value is distinguished, it should become current and the
+ current can become pending */
+ if (value_distinguished_at_csn (entry, 1, value,
+ entry->sv_attr.current_value.presence_csn))
+ {
+ if (entry->sv_attr.pending_value == NULL)
+ {
+ entry->sv_attr.pending_value = (Value_State*)
+ malloc (sizeof (Value_State));
+ memcpy (entry->sv_attr.pending_value, &entry->sv_attr.current_value,
+ sizeof (Value_State));
+ }
+
+ memcpy (&entry->sv_attr.current_value, value, sizeof (Value_State));
+ }
+ }
+ else /* new value is after the current value */
+ {
+ /* if current value is not distinguished, new value should
+ become distinguished */
+ if (!value_distinguished_at_csn (entry, 1, &entry->sv_attr.current_value,
+ value->presence_csn))
+ {
+ memcpy (&entry->sv_attr.current_value, value, sizeof (Value_State));
+ }
+ else /* current value is distinguished - check if new value should replace
+ the pending value */
+ { if (entry->sv_attr.pending_value)
+ {
+ if (value->presence_csn > entry->sv_attr.pending_value->presence_csn)
+ {
+ memcpy (entry->sv_attr.pending_value, value, sizeof (Value_State));
+ }
+ }
+ else
+ {
+ entry->sv_attr.pending_value = (Value_State*)malloc (sizeof (Value_State));
+ memcpy (entry->sv_attr.pending_value, value, sizeof (Value_State));
+ }
+ }
+ }
+ }
+ }
+
+ /* update the attribute state */
+ purge_sv_attr_state (entry);
+
+ /* set attribute state */
+ if (entry->sv_attr.attr_state.delete_csn != NOT_PRESENT &&
+ !value_distinguished_at_csn (entry, 1, &entry->sv_attr.current_value,
+ entry->sv_attr.attr_state.delete_csn))
+ {
+ entry->sv_attr.attr_state.present = 0;
+ }
+ else
+ {
+ entry->sv_attr.attr_state.present = 1;
+ }
+}
+
+int value_distinguished_at_csn (Entry_State *entry, int sv_attr, Value_State *value, int csn)
+{
+ int i;
+
+ for (i = 0; i < entry->dn_csn_count; i++)
+ {
+ if (entry->dn_csns[i].csn > csn)
+ break;
+ }
+
+ /* i is never equal to 0 because the csn of the first element is always
+ smaller than csn of any operation we can receive */
+ return (entry->dn_csns[i-1].value_index == value->value_index &&
+ entry->dn_csns[i-1].sv_attr == sv_attr);
+}
+
+int compare_entry_state (Entry_State *entry1, Entry_State *entry2, int run)
+{
+ int i;
+ int error = 0;
+
+ error = compare_entry_state_quick (entry1, entry2, run);
+
+ if (error)
+ return 3;
+
+ /* compare dnc_csn list */
+ if (entry1->dn_csn_count != entry2->dn_csn_count)
+ {
+ fprintf (sim.fout, "dn_csn count is %d for run 1 and %d for run %d\n",
+ entry1->dn_csn_count, entry2->dn_csn_count, run);
+ error = 1;
+ }
+
+ for (i = 0; i < entry1->dn_csn_count; i++)
+ {
+ if (entry1->dn_csns [i].csn != entry2->dn_csns [i].csn ||
+ entry1->dn_csns [i].sv_attr != entry2->dn_csns [i].sv_attr ||
+ entry1->dn_csns [i].value_index != entry2->dn_csns [i].value_index)
+ {
+ fprintf (sim.fout,"elements %d of dn csn list are different:\n"
+ "\tfirst run: csn - %d, attr - %s, value - %s\n"
+ "\t%d run: csn - %d, attr - %s value - %s\n", i,
+ entry1->dn_csns [i].csn,
+ entry1->dn_csns [i].sv_attr ? SV_ATTR_NAME : MV_ATTR_NAME,
+ g_values[entry1->dn_csns [i].value_index],
+ run, entry2->dn_csns [i].csn,
+ entry2->dn_csns [i].sv_attr ? SV_ATTR_NAME : MV_ATTR_NAME,
+ g_values[entry2->dn_csns [i].value_index]);
+
+ error = 1;
+ }
+ }
+
+ error |= compare_sv_attr_state (&entry1->sv_attr, &entry2->sv_attr, run);
+
+ error |= compare_mv_attr_state (&entry1->mv_attr, &entry2->mv_attr, run);
+
+ if (error != 0)
+ {
+ return 1;
+ }
+ else
+ return 0;
+}
+
+/* just compare if the same attributes and values are present */
+int compare_entry_state_quick (Entry_State *entry1, Entry_State *entry2, int run)
+{
+ int error;
+
+ error = compare_sv_attr_state_quick (&entry1->sv_attr, &entry2->sv_attr, run);
+
+ error |= compare_mv_attr_state_quick (&entry1->mv_attr, &entry2->mv_attr, run);
+
+ return error;
+}
+
+int compare_sv_attr_state_quick (SV_Attr_State *sv_attr1, SV_Attr_State *sv_attr2, int run)
+{
+ int error = 0;
+ if (sv_attr1->attr_state.present != sv_attr2->attr_state.present)
+ {
+ fprintf (sim.fout, "singlevalued attribute is %s present in the first run "
+ "but is %s present in the %d run\n",
+ sv_attr1->attr_state.present ? "" : "not",
+ sv_attr2->attr_state.present ? "" : "not", run);
+ return 1;
+ }
+
+ if (sv_attr1->attr_state.present &&
+ sv_attr1->current_value.value_index != sv_attr2->current_value.value_index)
+ {
+ fprintf (sim.fout, "different values for singlevalued attribute: %s for the \n"
+ "first run and %s for the %d run\n",
+ g_values [sv_attr1->current_value.value_index],
+ g_values [sv_attr2->current_value.value_index], run);
+ return 1;
+ }
+
+ return 0;
+}
+
+int compare_mv_attr_state_quick (MV_Attr_State *mv_attr1, MV_Attr_State *mv_attr2, int run)
+{
+ int i;
+ int error = 0;
+
+ if (mv_attr1->attr_state.present != mv_attr2->attr_state.present)
+ {
+ fprintf (sim.fout, "multivalued attribute is %s present in the first run "
+ "but is %s present in the %d run\n",
+ mv_attr1->attr_state.present ? "" : "not",
+ mv_attr2->attr_state.present ? "" : "not", run);
+ return 1;
+ }
+
+ /* value count does not change during the iteration, so we don't have
+ to check if the count is the same for both attributes */
+ for (i = 0; i < mv_attr1->value_count; i++)
+ {
+ if (mv_attr1->values[i].present != mv_attr2->values[i].present)
+ {
+ fprintf (sim.fout, "value %s is %s present in the multivalued attribute\n"
+ "in the first run but %s present in the %d run\n",
+ g_values[i], mv_attr1->values[i].present ? "" : "not",
+ mv_attr2->values[i].present ? "" : "not", run);
+ error = 1;
+ }
+ }
+
+ return error;
+}
+
+int compare_sv_attr_state (SV_Attr_State *sv_attr1, SV_Attr_State *sv_attr2, int run)
+{
+ int error = 0;
+
+ if (sv_attr1->attr_state.delete_csn != sv_attr2->attr_state.delete_csn)
+ {
+ fprintf (sim.fout, "singlevalued attribute deletion csn is %d for run 1 "
+ "but is %d for run %d\n", sv_attr1->attr_state.delete_csn,
+ sv_attr2->attr_state.delete_csn, run);
+ error = 1;
+ }
+
+ error |= compare_value_state (&sv_attr1->current_value, &sv_attr2->current_value, run);
+
+ if ((sv_attr1->pending_value && !sv_attr1->pending_value) ||
+ (!sv_attr1->pending_value && sv_attr1->pending_value))
+ {
+ fprintf (sim.fout, "pending value is %s present in the singlevalued attribute\n"
+ " in the first run but is %s in the %d run\n",
+ sv_attr1->pending_value ? "" : "not",
+ sv_attr2->pending_value ? "" : "not", run);
+
+ return 1;
+ }
+
+ if (sv_attr1->pending_value)
+ error |= compare_value_state (sv_attr1->pending_value, sv_attr2->pending_value, run);
+
+ return 0;
+}
+
+int compare_mv_attr_state (MV_Attr_State *mv_attr1, MV_Attr_State *mv_attr2, int run)
+{
+ int error = 0;
+ int i;
+
+ if (mv_attr1->attr_state.delete_csn != mv_attr2->attr_state.delete_csn)
+ {
+ fprintf (sim.fout, "multivalued attribute deletion csn is %d for run 1 "
+ "but is %d for run %d\n", mv_attr1->attr_state.delete_csn,
+ mv_attr2->attr_state.delete_csn, run);
+ error = 1;
+ }
+
+ for (i = 0; i < mv_attr1->value_count; i++)
+ {
+ error |= compare_value_state (&mv_attr1->values[i], &mv_attr2->values[i], run);
+ }
+
+ return error;
+}
+
+int compare_value_state (Value_State *value1, Value_State *value2, int run)
+{
+ int error = 0;
+
+ if (value1->presence_csn != value2->presence_csn)
+ {
+ fprintf (sim.fout, "multivalued attribute: presence csn for value %s is %d "
+ "in run 1 but is %d in run %d\n", g_values[value1->value_index],
+ value1->presence_csn, value2->presence_csn, run);
+ error = 1;
+ }
+
+ if (value1->delete_csn != value2->delete_csn)
+ {
+ fprintf (sim.fout, "multivalued attribute: delete csn for value %s is %d in run 1 "
+ "but is %d in run %d\n", g_values[value1->value_index],
+ value1->delete_csn, value2->delete_csn, run);
+ error = 1;
+ }
+
+ return error;
+}
+
+int dn_csn_add (Entry_State *entry, int sv_attr, int value_index, int csn)
+{
+ int i;
+
+ for (i = 0; i < entry->dn_csn_count; i++)
+ {
+ if (entry->dn_csns[i].csn > csn)
+ break;
+ }
+
+ if (i < entry->dn_csn_count)
+ {
+ memcpy (&(entry->dn_csns[i+1]), &(entry->dn_csns[i]),
+ (entry->dn_csn_count - i) * sizeof (Dn_Csn));
+ }
+
+ entry->dn_csns[i].csn = csn;
+ entry->dn_csns[i].sv_attr = sv_attr;
+ entry->dn_csns[i].value_index = value_index;
+ entry->dn_csn_count ++;
+
+ return i;
+}
+
+void dump_operations (Operation *ops, int op_count, int *order)
+{
+ int index;
+ int i;
+
+ for (i = 0; i < op_count; i ++)
+ {
+ if (order == NULL) /* current order */
+ index = i;
+ else
+ index = order [i];
+
+ dump_operation (&ops[index]);
+ }
+
+ fprintf (sim.fout, "\n");
+}
+
+void dump_operation (Operation *op)
+{
+ switch (op->type)
+ {
+ case OP_ADD_VALUE:
+ fprintf (sim.fout, "\t%d add value %s to %s\n", op->csn,
+ g_values [op->value_index],
+ op->sv_attr ? SV_ATTR_NAME : MV_ATTR_NAME);
+ break;
+ case OP_DELETE_VALUE:
+ fprintf (sim.fout, "\t%d delete value %s from %s\n", op->csn,
+ g_values [op->value_index],
+ op->sv_attr ? SV_ATTR_NAME : MV_ATTR_NAME);
+ break;
+ case OP_DELETE_ATTR:
+ fprintf (sim.fout, "\t%d delete %s attribute\n", op->csn,
+ op->sv_attr ? SV_ATTR_NAME : MV_ATTR_NAME);
+ break;
+ case OP_RENAME_ENTRY:
+ fprintf (sim.fout, "\t%d rename entry to %s=%s", op->csn,
+ op->sv_attr ? SV_ATTR_NAME : MV_ATTR_NAME,
+ g_values [op->value_index]);
+ if (op->delete_old_rdn)
+ fprintf (sim.fout, " delete old rdn %s=%s\n",
+ op->old_rdn_sv_attr ? SV_ATTR_NAME : MV_ATTR_NAME,
+ g_values [op->old_rdn_value_index]);
+ else
+ fprintf (sim.fout, "\n");
+ break;
+ }
+}
+
+void dump_perm_table (int **perm_table, int op_count)
+{
+ int i, j;
+ int perm_count = get_perm_count (op_count);
+
+ for (i = 0; i < op_count; i++)
+ {
+ for (j = 0; j < perm_count; j++)
+ {
+ fprintf (sim.fout, "%d ", perm_table [j][i]);
+ }
+
+ fprintf (sim.fout, "\n");
+ }
+}
+
+void dump_entry_state (Entry_State *entry)
+{
+ dump_dn_csn_list (entry);
+
+ dump_sv_attr_state (&entry->sv_attr);
+ dump_mv_attr_state (&entry->mv_attr);
+
+ fprintf (sim.fout, "\n");
+}
+
+void dump_sv_attr_state (SV_Attr_State *sv_attr)
+{
+ fprintf (sim.fout, "\tattribute %s is %s present", SV_ATTR_NAME,
+ sv_attr->attr_state.present ? "" : "not");
+ if (sv_attr->attr_state.present)
+ {
+ fprintf (sim.fout, " and has the value of %s\n",
+ g_values[sv_attr->current_value.value_index]);
+ }
+ else
+ {
+ fprintf (sim.fout, "\n");
+ }
+
+ if (sim.verbose)
+ {
+ fprintf (sim.fout, "\t\tdeletion csn: %d\n", sv_attr->attr_state.delete_csn);
+ fprintf (sim.fout, "\t\tcurrent value: ");
+ dump_value_state (&sv_attr->current_value, 1/* for single valued attr */);
+ if (sv_attr->pending_value)
+ {
+ fprintf (sim.fout, "\t\tpending value: ");
+ dump_value_state (sv_attr->pending_value, 1/* for single valued attr */);
+ }
+ }
+}
+
+void dump_mv_attr_state (MV_Attr_State *mv_attr)
+{
+ int i;
+
+ fprintf (sim.fout, "\tattribute %s is %s present\n", MV_ATTR_NAME,
+ mv_attr->attr_state.present ? "" : "not");
+
+ if (sim.verbose)
+ {
+ fprintf (sim.fout, "\t\tdeletion csn: %d\n", mv_attr->attr_state.delete_csn);
+ }
+
+ for (i = 0; i < mv_attr->value_count; i++)
+ {
+ dump_value_state (&(mv_attr->values[i]), 0);
+ }
+}
+
+void dump_value_state (Value_State *value, int sv_attr)
+{
+ if (!sv_attr)
+ {
+ fprintf (sim.fout, "\tvalue %s is %s present\n", g_values[value->value_index],
+ value->present ? "" : "not");
+ }
+ else
+ {
+ fprintf (sim.fout, "%s\n", g_values[value->value_index]);
+ }
+
+ if (sim.verbose)
+ {
+ fprintf (sim.fout, "\t\t\tpresence csn: %d\n", value->presence_csn);
+ fprintf (sim.fout, "\t\t\tdeletion value csn: %d\n", value->delete_csn);
+ }
+}
+
+void dump_dn_csn_list (Entry_State *entry)
+{
+ int i;
+
+ fprintf (sim.fout, "\tdn csn list: \n");
+ for (i = 0; i < entry->dn_csn_count; i++)
+ {
+ fprintf (sim.fout, "\t\t %s=%s, csn: %d\n",
+ entry->dn_csns[i].sv_attr ? SV_ATTR_NAME : MV_ATTR_NAME,
+ g_values[entry->dn_csns[i].value_index], entry->dn_csns[i].csn);
+ }
+}
+
+/* misc functions */
+int max_val (int a, int b)
+{
+ if (a >= b)
+ return a;
+ else
+ return b;
+}
diff --git a/ldap/servers/plugins/replication/tests/makesim b/ldap/servers/plugins/replication/tests/makesim
new file mode 100755
index 00000000..0cedd6e1
--- /dev/null
+++ b/ldap/servers/plugins/replication/tests/makesim
@@ -0,0 +1,58 @@
+# BEGIN COPYRIGHT BLOCK
+# Copyright 2001 Sun Microsystems, Inc.
+# Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+# All rights reserved.
+# END COPYRIGHT BLOCK
+#
+# gnu makefile for LDAP Server tools.
+#
+
+MCOM_ROOT = ../../../../../..
+LDAP_SRC = ../../../..
+
+NOSTDCLEAN=true # don't let nsconfig.mk define target clean
+NOSTDSTRIP=true # don't let nsconfig.mk define target strip
+
+OBJDEST = $(OBJDIR)/lib/replication-plugin
+BINDIR = $(OBJDIR)/bin
+
+include $(MCOM_ROOT)/netsite/nsdefs.mk
+include $(MCOM_ROOT)/netsite/nsconfig.mk
+include $(LDAP_SRC)/nsldap.mk
+
+LDFLAGS += $(EXLDFLAGS)
+
+ifeq ($(ARCH), WINNT)
+SUBSYSTEM=console
+endif
+
+DEPLIBS=
+
+EXTRA_LIBS_DEP =
+
+EXTRA_LIBS =
+
+ifeq ($(ARCH), WINNT)
+EXTRA_LIBS += user32.lib
+endif
+
+DNP_SIM = $(addsuffix $(EXE_SUFFIX), \
+ $(addprefix $(BINDIR)/, dnp_sim))
+
+
+all: $(OBJDEST) $(BINDIR) $(DNP_SIM)
+
+$(DNP_SIM): $(OBJDEST)/dnp_sim3.o $(EXTRA_LIBS_DEP)
+ $(LINK_EXE) $(OBJDEST)/dnp_sim3.o \
+ $(EXTRA_LIBS) $<
+
+
+$(OBJDEST):
+ $(MKDIR) $(OBJDEST)
+
+$(BINDIR):
+ $(MKDIR) $(BINDIR)
+
+clean:
+ -$(RM) $(ALL_OBJS)
+ -$(RM) $(BINS)
diff --git a/ldap/servers/plugins/replication/urp.c b/ldap/servers/plugins/replication/urp.c
new file mode 100644
index 00000000..a4dc86f9
--- /dev/null
+++ b/ldap/servers/plugins/replication/urp.c
@@ -0,0 +1,1282 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+/*
+ * urp.c - Update Resolution Procedures
+ */
+
+#include "slapi-plugin.h"
+#include "repl.h"
+#include "repl5.h"
+#include "urp.h"
+
+extern int slapi_log_urp;
+
+static int urp_add_resolve_parententry (Slapi_PBlock *pb, char *sessionid, Slapi_Entry *entry, Slapi_Entry *parententry, CSN *opcsn);
+static int urp_annotate_dn (char *sessionid, Slapi_Entry *entry, CSN *opcsn, const char *optype);
+static int urp_naming_conflict_removal (Slapi_PBlock *pb, char *sessionid, CSN *opcsn, const char *optype);
+static int mod_namingconflict_attr (const char *uniqueid, const char*entrydn, const char *conflictdn, CSN *opcsn);
+static int del_replconflict_attr (Slapi_Entry *entry, CSN *opcsn, int opflags);
+static char *get_dn_plus_uniqueid(char *sessionid,const char *olddn,const char *uniqueid);
+static char *get_rdn_plus_uniqueid(char *sessionid,const char *olddn,const char *uniqueid);
+static void set_pblock_dn (Slapi_PBlock* pb,int pblock_parameter,char *newdn);
+static int is_suffix_entry (Slapi_PBlock *pb, Slapi_Entry *entry, Slapi_DN **parenddn);
+
+/*
+ * Return 0 for OK, -1 for Error.
+ */
+int
+urp_modify_operation( Slapi_PBlock *pb )
+{
+ Slapi_Entry *modifyentry= NULL;
+ int op_result= 0;
+ int rc= 0; /* OK */
+
+ if ( slapi_op_abandoned(pb) )
+ {
+ return rc;
+ }
+
+ slapi_pblock_get( pb, SLAPI_MODIFY_EXISTING_ENTRY, &modifyentry );
+
+ if(modifyentry!=NULL)
+ {
+ /*
+ * The entry to be modified exists.
+ * - the entry could be a tombstone... but that's OK.
+ * - the entry could be glue... that may not be OK. JCMREPL
+ */
+ rc= 0; /* OK, Modify the entry */
+ PROFILE_POINT; /* Modify Conflict; Entry Exists; Apply Modification */
+ }
+ else
+ {
+ /*
+ * The entry to be modified could not be found.
+ */
+ op_result= LDAP_NO_SUCH_OBJECT;
+ slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result);
+ rc= -1; /* Must discard this Modification */
+ PROFILE_POINT; /* Modify Conflict; Entry Does Not Exist; Discard Modification */
+ }
+ return rc;
+}
+
+/*
+ * Return 0 for OK,
+ * -1 for Ignore or Error depending on SLAPI_RESULT_CODE,
+ * >0 for action code
+ * Action Code Bit 0: Fetch existing entry.
+ * Action Code Bit 1: Fetch parent entry.
+ * The function is called as a be pre-op on consumers.
+ */
+int
+urp_add_operation( Slapi_PBlock *pb )
+{
+ Slapi_Entry *existing_uniqueid_entry;
+ Slapi_Entry *existing_dn_entry;
+ Slapi_Entry *addentry;
+ const char *adduniqueid;
+ CSN *opcsn;
+ const char *basedn;
+ char sessionid[REPL_SESSION_ID_SIZE];
+ int r;
+ int op_result= 0;
+ int rc= 0; /* OK */
+
+ if ( slapi_op_abandoned(pb) )
+ {
+ return rc;
+ }
+
+ slapi_pblock_get( pb, SLAPI_ADD_EXISTING_UNIQUEID_ENTRY, &existing_uniqueid_entry );
+ if (existing_uniqueid_entry!=NULL)
+ {
+ /*
+ * An entry with this uniqueid already exists.
+ * - It could be a replay of the same Add, or
+ * - It could be a UUID generation collision, or
+ */
+ op_result = LDAP_SUCCESS;
+ slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result);
+ rc= -1; /* Ignore this Operation */
+ PROFILE_POINT; /* Add Conflict; UniqueID Exists; Ignore */
+ goto bailout;
+ }
+
+ get_repl_session_id (pb, sessionid, &opcsn);
+ slapi_pblock_get( pb, SLAPI_ADD_ENTRY, &addentry );
+ slapi_pblock_get( pb, SLAPI_ADD_EXISTING_DN_ENTRY, &existing_dn_entry );
+ if (existing_dn_entry==NULL) /* The target DN does not exist */
+ {
+ /* Check for parent entry... this could be an orphan. */
+ Slapi_Entry *parententry;
+ slapi_pblock_get( pb, SLAPI_ADD_PARENT_ENTRY, &parententry );
+ rc = urp_add_resolve_parententry (pb, sessionid, addentry, parententry, opcsn);
+ PROFILE_POINT; /* Add Entry */
+ goto bailout;
+ }
+
+ /*
+ * Naming conflict: an entry with the target DN already exists.
+ * Compare the DistinguishedNameCSN of the existing entry
+ * and the OperationCSN. The smaller CSN wins. The loser changes
+ * its RDN to uniqueid+baserdn, and adds operational attribute
+ * ATTR_NSDS5_REPLCONFLIC.
+ */
+ basedn = slapi_entry_get_ndn (addentry);
+ adduniqueid = slapi_entry_get_uniqueid (addentry);
+ r = csn_compare (entry_get_dncsn(existing_dn_entry), opcsn);
+ if (r<0)
+ {
+ /* Entry to be added is a loser */
+ char *newdn= get_dn_plus_uniqueid (sessionid, basedn, adduniqueid);
+ if(newdn==NULL)
+ {
+ op_result= LDAP_OPERATIONS_ERROR;
+ slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result);
+ rc= -1; /* Abort this Operation */
+ PROFILE_POINT; /* Add Conflict; Entry Exists; Unique ID already in RDN - Abort this update. */
+ }
+ else
+ {
+ /* Add the nsds5ReplConflict attribute in the mods */
+ Slapi_Attr *attr = NULL;
+ Slapi_Value **vals = NULL;
+ Slapi_RDN *rdn;
+ char buf[BUFSIZ];
+
+ sprintf(buf, "%s %s", REASON_ANNOTATE_DN, basedn);
+ if (slapi_entry_attr_find (addentry, ATTR_NSDS5_REPLCONFLICT, &attr) == 0)
+ {
+ /* ATTR_NSDS5_REPLCONFLICT exists */
+ slapi_log_error (SLAPI_LOG_FATAL, sessionid, "New entry has nsds5ReplConflict already\n");
+ vals = attr_get_present_values (attr); /* this returns a pointer to the contents */
+ }
+ if ( vals == NULL || *vals == NULL )
+ {
+ /* Add new attribute */
+ slapi_entry_add_string (addentry, ATTR_NSDS5_REPLCONFLICT, buf);
+ }
+ else
+ {
+ /*
+ * Replace old attribute. We don't worry about the index
+ * change here since the entry is yet to be added.
+ */
+ slapi_value_set_string (*vals, buf);
+ }
+ slapi_entry_set_dn (addentry,slapi_ch_strdup(newdn));
+ set_pblock_dn(pb,SLAPI_ADD_TARGET,newdn); /* consumes newdn */
+
+ rdn = slapi_rdn_new_sdn ( slapi_entry_get_sdn_const(addentry) );
+ slapi_log_error (slapi_log_urp, sessionid,
+ "Naming conflict ADD. Add %s instead\n", slapi_rdn_get_rdn(rdn) );
+ slapi_rdn_free(&rdn);
+
+ rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_EXISTING_DN_ENTRY);
+ PROFILE_POINT; /* Add Conflict; Entry Exists; Rename Operation Entry */
+ }
+ }
+ else if(r>0)
+ {
+ /* Existing entry is a loser */
+ if (!urp_annotate_dn(sessionid, existing_dn_entry, opcsn, "ADD"))
+ {
+ op_result= LDAP_OPERATIONS_ERROR;
+ slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result);
+ rc= -1; /* Ignore this Operation */
+ }
+ else
+ {
+ /* The backend add code should now search for the existing entry again. */
+ rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_EXISTING_DN_ENTRY);
+ rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_PARENT_ENTRY);
+ }
+ PROFILE_POINT; /* Add Conflict; Entry Exists; Rename Existing Entry */
+ }
+ else /* r==0 */
+ {
+ /* The CSN of the Operation and the Entry DN are the same.
+ * This could only happen if:
+ * a) There are two replicas with the same ReplicaID.
+ * b) We've seen the Operation before.
+ * Let's go with (b) and ignore the little bastard.
+ */
+ op_result= LDAP_SUCCESS;
+ slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result);
+ rc= -1; /* Ignore this Operation */
+ PROFILE_POINT; /* Add Conflict; Entry Exists; Same CSN */
+ }
+
+bailout:
+ return rc;
+}
+
+/*
+ * Return 0 for OK, -1 for Error, >0 for action code
+ * Action Code Bit 0: Fetch existing entry.
+ * Action Code Bit 1: Fetch parent entry.
+ */
+int
+urp_modrdn_operation( Slapi_PBlock *pb )
+{
+ slapi_operation_parameters *op_params = NULL;
+ Slapi_Entry *parent_entry;
+ Slapi_Entry *new_parent_entry;
+ Slapi_DN *newsuperior = NULL;
+ char *newsuperiordn;
+ Slapi_DN *parentdn = NULL;
+ Slapi_Entry *target_entry;
+ Slapi_Entry *existing_entry;
+ const CSN *target_entry_dncsn;
+ CSN *opcsn= NULL;
+ char *op_uniqueid = NULL;
+ const char *existing_uniqueid = NULL;
+ const char *target_dn;
+ const char *existing_dn;
+ char *newrdn;
+ char sessionid[REPL_SESSION_ID_SIZE];
+ int r;
+ int op_result= 0;
+ int rc= 0; /* OK */
+ int del_old_replconflict_attr = 0;
+
+ if ( slapi_op_abandoned(pb) )
+ {
+ return rc;
+ }
+
+ slapi_pblock_get (pb, SLAPI_MODRDN_TARGET_ENTRY, &target_entry);
+ if(target_entry==NULL)
+ {
+ /* An entry can't be found for the Unique Identifier */
+ op_result= LDAP_NO_SUCH_OBJECT;
+ slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result);
+ rc= -1; /* No entry to modrdn */
+ PROFILE_POINT; /* ModRDN Conflict; Entry does not Exist; Discard ModRDN */
+ goto bailout;
+ }
+
+ get_repl_session_id (pb, sessionid, &opcsn);
+ target_entry_dncsn = entry_get_dncsn (target_entry);
+ if ( csn_compare (target_entry_dncsn, opcsn) >= 0 )
+ {
+ /*
+ * The Operation CSN is not newer than the DN CSN.
+ * Either we're beaten by another ModRDN or we've applied the op.
+ */
+ op_result= LDAP_SUCCESS;
+ slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result);
+ rc= -1; /* Ignore the modrdn */
+ PROFILE_POINT; /* ModRDN Conflict; Entry with Target DN Exists; OPCSN is not newer. */
+ goto bailout;
+ }
+
+ /* The DN CSN is older than the Operation CSN. Apply the operation */
+ target_dn = slapi_entry_get_dn_const ( target_entry);
+ slapi_pblock_get(pb, SLAPI_MODRDN_NEWRDN, &newrdn);
+ slapi_pblock_get(pb, SLAPI_TARGET_UNIQUEID, &op_uniqueid);
+ slapi_pblock_get(pb, SLAPI_MODRDN_PARENT_ENTRY, &parent_entry);
+ slapi_pblock_get(pb, SLAPI_MODRDN_NEWPARENT_ENTRY, &new_parent_entry);
+ slapi_pblock_get(pb, SLAPI_MODRDN_NEWSUPERIOR, &newsuperiordn);
+
+ if ( is_tombstone_entry (target_entry) )
+ {
+ /*
+ * It is a non-trivial task to rename a tombstone.
+ * This op has been ignored so far by
+ * setting SLAPI_RESULT_CODE to LDAP_NO_SUCH_OBJECT
+ * and rc to -1.
+ */
+
+ /* Turn the tombstone to glue before rename it */
+ /*
+ op_result = tombstone_to_glue (pb, sessionid, target_entry,
+ slapi_entry_get_sdn (target_entry), "renameTombstone", opcsn);
+ */
+ op_result = LDAP_NO_SUCH_OBJECT;
+ slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result);
+ if (op_result == 0)
+ {
+ /*
+ * Remember to turn this entry back to tombstone in post op.
+ * We'll just borrow an obsolete pblock type here.
+ */
+ slapi_pblock_set (pb, SLAPI_URP_TOMBSTONE_UNIQUEID, strdup(op_uniqueid));
+ rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_TARGET_ENTRY);
+ rc = 0;
+ }
+ else
+ {
+ rc = -1;
+ }
+ PROFILE_POINT; /* ModRDN Conflict; Entry with Target DN Exists; OPCSN is not newer. */
+ goto bailout;
+ }
+
+ slapi_pblock_get(pb, SLAPI_MODRDN_EXISTING_ENTRY, &existing_entry);
+ if(existing_entry!=NULL)
+ {
+ /*
+ * An entry with the target DN already exists.
+ * The smaller dncsn wins. The loser changes its RDN to
+ * uniqueid+baserdn, and adds operational attribute
+ * ATTR_NSDS5_REPLCONFLIC
+ */
+
+ existing_uniqueid = slapi_entry_get_uniqueid (existing_entry);
+ existing_dn = slapi_entry_get_dn_const ( existing_entry);
+
+ /*
+ * Dismiss the operation if the existing entry is the same as the target one.
+ */
+ if (strcmp(op_uniqueid, existing_uniqueid) == 0) {
+ op_result= LDAP_SUCCESS;
+ slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result);
+ rc = -1; /* Ignore the op */
+ PROFILE_POINT; /* ModRDN Replay */
+ goto bailout;
+ }
+
+ r= csn_compare ( entry_get_dncsn (existing_entry), opcsn);
+ if (r == 0)
+ {
+ /*
+ * The CSN of the Operation and the Entry DN are the same
+ * but the uniqueids are not.
+ * There might be two replicas with the same ReplicaID.
+ */
+ slapi_log_error(SLAPI_LOG_FATAL, sessionid,
+ "Duplicated CSN for different uniqueids [%s][%s]",
+ existing_uniqueid, op_uniqueid);
+ op_result= LDAP_OPERATIONS_ERROR;
+ slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result);
+ rc= -1; /* Abort */
+ PROFILE_POINT; /* ModRDN Conflict; Duplicated CSN for Different Entries */
+ goto bailout;
+ }
+
+ if(r<0)
+ {
+ /* The target entry is a loser */
+
+ char *newrdn_with_uniqueid;
+ newrdn_with_uniqueid= get_rdn_plus_uniqueid (sessionid, newrdn, op_uniqueid);
+ if(newrdn_with_uniqueid==NULL)
+ {
+ op_result= LDAP_OPERATIONS_ERROR;
+ slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result);
+ rc= -1; /* Ignore this Operation */
+ PROFILE_POINT; /* ModRDN Conflict; Entry with Target DN Exists;
+ Unique ID already in RDN - Change to Lost and Found entry */
+ goto bailout;
+ }
+ mod_namingconflict_attr (op_uniqueid, target_dn, existing_dn, opcsn);
+ set_pblock_dn (pb, SLAPI_MODRDN_NEWRDN, newrdn_with_uniqueid);
+ slapi_log_error(slapi_log_urp, sessionid,
+ "Naming conflict MODRDN. Rename target entry to %s\n",
+ newrdn_with_uniqueid );
+
+ rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_EXISTING_DN_ENTRY);
+ PROFILE_POINT; /* ModRDN Conflict; Entry with Target DN Exists; Rename Operation Entry */
+ goto bailout;
+ }
+
+ if ( r>0 )
+ {
+ /* The existing entry is a loser */
+
+ int resolve = urp_annotate_dn (sessionid, existing_entry, opcsn, "MODRDN");
+ if(!resolve)
+ {
+ op_result= LDAP_OPERATIONS_ERROR;
+ slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result);
+ rc= -1; /* Abort this Operation */
+ goto bailout;
+ }
+ rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_EXISTING_DN_ENTRY);
+ rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_NEWPARENT_ENTRY);
+ if (LDAP_NO_SUCH_OBJECT == resolve) {
+ /* This means that existing_dn_entry did not really exist!!!
+ * This indicates that a get_copy_of_entry -> dn2entry returned
+ * an entry (existing_dn_entry) that was already removed from the ldbm.
+ * This is bad, because it indicates a dn cache or DB corruption.
+ * However, as far as the conflict is concerned, this error is harmless:
+ * if the existing_dn_entry did not exist in the first place, there was no
+ * conflict!! Return 0 for success to break the ldbm_back_modrdn loop
+ * and get out of this inexistent conflict resolution ASAP.
+ */
+ rc = 0;
+ }
+ /* Set flag to remove possible old naming conflict */
+ del_old_replconflict_attr = 1;
+ PROFILE_POINT; /* ModRDN Conflict; Entry with Target DN Exists; Rename Entry with Target DN */
+ goto bailout;
+ }
+ }
+ else
+ {
+ /*
+ * No entry with the target DN exists.
+ */
+
+ /* Set flag to remove possible old naming conflict */
+ del_old_replconflict_attr = 1;
+
+ if(new_parent_entry!=NULL)
+ {
+ /* The new superior entry exists */
+ rc= 0; /* OK, Apply the ModRDN */
+ PROFILE_POINT; /* ModRDN Conflict; OK */
+ goto bailout;
+ }
+
+ /* The new superior entry doesn't exist */
+
+ slapi_pblock_get(pb, SLAPI_MODRDN_NEWSUPERIOR, &newsuperiordn);
+ if(newsuperiordn == NULL)
+ {
+ /* (new_parent_entry==NULL && newsuperiordn==NULL)
+ * This is ok - SLAPI_MODRDN_NEWPARENT_ENTRY will
+ * only be set if SLAPI_MODRDN_NEWSUPERIOR was
+ * suplied by the client. If it wasn't, we're just
+ * changing the RDN of the entry. In that case,
+ * if the entry exists, its parent won't change
+ * when it's renamed, and therefore we can assume
+ * its parent exists.
+ */
+ rc=0;
+ PROFILE_POINT; /* ModRDN OK */
+ goto bailout;
+ }
+
+ newsuperior= slapi_sdn_new_dn_byval(newsuperiordn);
+
+ if((0 == slapi_sdn_compare (slapi_entry_get_sdn(parent_entry), newsuperior)) ||
+ is_suffix_dn (pb, newsuperior, &parentdn) )
+ {
+ /*
+ * The new superior is the same as the current one, or
+ * this entry is a suffix whose parent can be absent.
+ */
+ rc= 0; /* OK, Move the entry */
+ PROFILE_POINT; /* ModRDN Conflict; Absent Target Parent; Create Suffix Entry */
+ goto bailout;
+ }
+
+ /*
+ * This entry is not a suffix entry, so the parent entry should exist.
+ * (This shouldn't happen in a ds5 server)
+ */
+ slapi_pblock_get ( pb, SLAPI_OPERATION_PARAMETERS, &op_params );
+ op_result = create_glue_entry (pb, sessionid, newsuperior,
+ op_params->p.p_modrdn.modrdn_newsuperior_address.uniqueid, opcsn);
+ if (LDAP_SUCCESS != op_result)
+ {
+ /*
+ * FATAL ERROR
+ * We should probably just abort the rename
+ * this will cause replication divergence requiring
+ * admin intercession
+ */
+ slapi_log_error( SLAPI_LOG_FATAL, sessionid,
+ "Parent %s couldn't be found, nor recreated as a glue entry\n", newsuperiordn );
+ op_result= LDAP_OPERATIONS_ERROR;
+ slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result);
+ rc = -1;
+ PROFILE_POINT;
+ goto bailout;
+ }
+
+ /* The backend add code should now search for the parent again. */
+ rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_NEWPARENT_ENTRY);
+ PROFILE_POINT; /* ModRDN Conflict; Absent Target Parent - Change to Lost and Found entry */
+ goto bailout;
+ }
+
+bailout:
+ if ( del_old_replconflict_attr && rc == 0 )
+ {
+ del_replconflict_attr (target_entry, opcsn, 0);
+ }
+ if ( parentdn )
+ slapi_sdn_free(&parentdn);
+ if ( newsuperior )
+ slapi_sdn_free(&newsuperior);
+ return rc;
+}
+
+/*
+ * Return 0 for OK, -1 for Error
+ */
+int
+urp_delete_operation( Slapi_PBlock *pb )
+{
+ Slapi_Entry *deleteentry;
+ CSN *opcsn= NULL;
+ char sessionid[REPL_SESSION_ID_SIZE];
+ int op_result= 0;
+ int rc= 0; /* OK */
+
+ if ( slapi_op_abandoned(pb) )
+ {
+ return rc;
+ }
+
+ slapi_pblock_get(pb, SLAPI_DELETE_EXISTING_ENTRY, &deleteentry);
+
+ if(deleteentry==NULL) /* uniqueid can't be found */
+ {
+ op_result= LDAP_NO_SUCH_OBJECT;
+ slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result);
+ rc= -1; /* Don't apply the Delete */
+ PROFILE_POINT; /* Delete Operation; Entry not exist. */
+ }
+ else if(is_tombstone_entry(deleteentry))
+ {
+ /* The entry is already a Tombstone, ignore this delete. */
+ op_result= LDAP_SUCCESS;
+ slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result);
+ rc = -1; /* Don't apply the Delete */
+ PROFILE_POINT; /* Delete Operation; Already a Tombstone. */
+ }
+ else /* The entry to be deleted exists and is not a tombstone */
+ {
+ get_repl_session_id (pb, sessionid, &opcsn);
+
+ /* Check if the entry has children. */
+ if(!slapi_entry_has_children(deleteentry))
+ {
+ /* Remove possible conflict attributes */
+ del_replconflict_attr (deleteentry, opcsn, 0);
+ rc= 0; /* OK, to delete the entry */
+ PROFILE_POINT; /* Delete Operation; OK. */
+ }
+ else
+ {
+ /* Turn this entry into a glue_absent_parent entry */
+ entry_to_glue(sessionid, deleteentry, REASON_RESURRECT_ENTRY, opcsn);
+
+ /* Turn the Delete into a No-Op */
+ op_result= LDAP_SUCCESS;
+ slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result);
+ rc = -1; /* Don't apply the Delete */
+ PROFILE_POINT; /* Delete Operation; Entry has children. */
+ }
+ }
+ return rc;
+}
+
+int urp_post_modrdn_operation (Slapi_PBlock *pb)
+{
+ CSN *opcsn;
+ char sessionid[REPL_SESSION_ID_SIZE];
+ char *tombstone_uniqueid;
+ Slapi_Entry *postentry;
+ Slapi_Operation *op;
+
+ /*
+ * Do not abandon the post op - the processed CSN needs to be
+ * committed to keep the consistency between the changelog
+ * and the backend DB.
+ * if ( slapi_op_abandoned(pb) ) return 0;
+ */
+
+ slapi_pblock_get (pb, SLAPI_URP_TOMBSTONE_UNIQUEID, &tombstone_uniqueid );
+ if (tombstone_uniqueid == NULL)
+ {
+ /*
+ * The entry is not resurrected from tombstone. Hence
+ * we need to check if any naming conflict with its
+ * old dn can be resolved.
+ */
+ slapi_pblock_get( pb, SLAPI_OPERATION, &op);
+ if (!operation_is_flag_set(op, OP_FLAG_REPL_FIXUP))
+ {
+ get_repl_session_id (pb, sessionid, &opcsn);
+ urp_naming_conflict_removal (pb, sessionid, opcsn, "MODRDN");
+ }
+ }
+ else
+ {
+ /*
+ * The entry was a resurrected tombstone.
+ * This could happen when we applied a rename
+ * to a tombstone to avoid server divergence. Now
+ * it's time to put the entry back to tombstone.
+ */
+ slapi_pblock_get ( pb, SLAPI_ENTRY_POST_OP, &postentry );
+ if (postentry && strcmp(tombstone_uniqueid, slapi_entry_get_uniqueid(postentry)) == 0)
+ {
+ entry_to_tombstone (pb, postentry);
+ }
+ slapi_ch_free ((void**)&tombstone_uniqueid);
+ slapi_pblock_set (pb, SLAPI_URP_TOMBSTONE_UNIQUEID, NULL);
+ }
+
+ return 0;
+}
+
+/*
+ * Conflict removal
+ */
+int
+urp_post_delete_operation( Slapi_PBlock *pb )
+{
+ Slapi_Operation *op;
+ Slapi_Entry *entry;
+ CSN *opcsn;
+ char sessionid[REPL_SESSION_ID_SIZE];
+ int op_result;
+
+ /*
+ * Do not abandon the post op - the processed CSN needs to be
+ * committed to keep the consistency between the changelog
+ * and the backend DB
+ * if ( slapi_op_abandoned(pb) ) return 0;
+ */
+
+ get_repl_session_id (pb, sessionid, &opcsn);
+
+ /*
+ * Conflict removal from the parent entry:
+ * If the parent is glue and has no more children,
+ * turn the parent to tombstone
+ */
+ slapi_pblock_get ( pb, SLAPI_DELETE_GLUE_PARENT_ENTRY, &entry );
+ if ( entry != NULL )
+ {
+ op_result = entry_to_tombstone ( pb, entry );
+ if ( op_result == LDAP_SUCCESS )
+ {
+ slapi_log_error ( slapi_log_urp, sessionid,
+ "Tombstoned glue entry %s since it has no more children\n",
+ slapi_entry_get_dn_const (entry) );
+ }
+ }
+
+ slapi_pblock_get( pb, SLAPI_OPERATION, &op);
+ if (!operation_is_flag_set(op, OP_FLAG_REPL_FIXUP))
+ {
+ /*
+ * Conflict removal from the peers of the old dn
+ */
+ urp_naming_conflict_removal (pb, sessionid, opcsn, "DEL");
+ }
+
+ return 0;
+}
+
+int
+urp_fixup_add_entry (Slapi_Entry *e, const char *target_uniqueid, const char *parentuniqueid, CSN *opcsn, int opflags)
+{
+ Slapi_PBlock *newpb;
+ Slapi_Operation *op;
+ int op_result;
+
+ newpb = slapi_pblock_new ();
+
+ /*
+ * Mark this operation as replicated, so that the front end
+ * doesn't add extra attributes.
+ */
+ slapi_add_entry_internal_set_pb (
+ newpb,
+ e,
+ NULL, /*Controls*/
+ repl_get_plugin_identity ( PLUGIN_MULTIMASTER_REPLICATION ),
+ OP_FLAG_REPLICATED | OP_FLAG_REPL_FIXUP | opflags);
+ if (target_uniqueid)
+ {
+ slapi_pblock_set( newpb, SLAPI_TARGET_UNIQUEID, (void*)target_uniqueid);
+ }
+ if (parentuniqueid)
+ {
+ struct slapi_operation_parameters *op_params;
+ slapi_pblock_get( newpb, SLAPI_OPERATION_PARAMETERS, &op_params );
+ op_params->p.p_add.parentuniqueid = (char*)parentuniqueid; /* Consumes parentuniqueid */
+ }
+ slapi_pblock_get ( newpb, SLAPI_OPERATION, &op );
+ operation_set_csn ( op, opcsn );
+
+ slapi_add_internal_pb ( newpb );
+ slapi_pblock_get ( newpb, SLAPI_PLUGIN_INTOP_RESULT, &op_result );
+ slapi_pblock_destroy ( newpb );
+
+ return op_result;
+}
+
+int
+urp_fixup_rename_entry (Slapi_Entry *entry, const char *newrdn, int opflags)
+{
+ Slapi_PBlock *newpb;
+ Slapi_Operation *op;
+ CSN *opcsn;
+ int op_result;
+
+ newpb = slapi_pblock_new();
+
+ /*
+ * Must mark this operation as replicated,
+ * so that the frontend doesn't add extra attributes.
+ */
+ slapi_rename_internal_set_pb (
+ newpb,
+ slapi_entry_get_dn_const (entry),
+ newrdn, /*NewRDN*/
+ NULL, /*NewSuperior*/
+ 0, /* !Delete Old RDNS */
+ NULL, /*Controls*/
+ slapi_entry_get_uniqueid (entry), /*uniqueid*/
+ repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION),
+ OP_FLAG_REPLICATED | OP_FLAG_REPL_FIXUP | opflags);
+
+ /* set operation csn to the entry's dncsn */
+ opcsn = (CSN *)entry_get_dncsn (entry);
+ slapi_pblock_get (newpb, SLAPI_OPERATION, &op);
+ operation_set_csn (op, opcsn);
+
+ slapi_modrdn_internal_pb(newpb);
+ slapi_pblock_get(newpb, SLAPI_PLUGIN_INTOP_RESULT, &op_result);
+
+ slapi_pblock_destroy(newpb);
+ return op_result;
+}
+
+int
+urp_fixup_delete_entry (const char *uniqueid, const char *dn, CSN *opcsn, int opflags)
+{
+ Slapi_PBlock *newpb;
+ Slapi_Operation *op;
+ int op_result;
+
+ newpb = slapi_pblock_new ();
+
+ /*
+ * Mark this operation as replicated, so that the front end
+ * doesn't add extra attributes.
+ */
+ slapi_delete_internal_set_pb (
+ newpb,
+ dn,
+ NULL, /*Controls*/
+ uniqueid, /*uniqueid*/
+ repl_get_plugin_identity ( PLUGIN_MULTIMASTER_REPLICATION ),
+ OP_FLAG_REPLICATED | OP_FLAG_REPL_FIXUP | opflags );
+ slapi_pblock_get ( newpb, SLAPI_OPERATION, &op );
+ operation_set_csn ( op, opcsn );
+
+ slapi_delete_internal_pb ( newpb );
+ slapi_pblock_get ( newpb, SLAPI_PLUGIN_INTOP_RESULT, &op_result );
+ slapi_pblock_destroy ( newpb );
+
+ return op_result;
+}
+
+int
+urp_fixup_modify_entry (const char *uniqueid, const char *dn, CSN *opcsn, Slapi_Mods *smods, int opflags)
+{
+ Slapi_PBlock *newpb;
+ Slapi_Operation *op;
+ int op_result;
+
+ newpb = slapi_pblock_new();
+
+ slapi_modify_internal_set_pb (
+ newpb,
+ dn,
+ slapi_mods_get_ldapmods_byref (smods),
+ NULL, /* Controls */
+ uniqueid,
+ repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION),
+ OP_FLAG_REPLICATED | OP_FLAG_REPL_FIXUP | opflags);
+
+ /* set operation csn */
+ slapi_pblock_get (newpb, SLAPI_OPERATION, &op);
+ operation_set_csn (op, opcsn);
+
+ /* do modify */
+ slapi_modify_internal_pb (newpb);
+ slapi_pblock_get (newpb, SLAPI_PLUGIN_INTOP_RESULT, &op_result);
+ slapi_pblock_destroy(newpb);
+
+ return op_result;
+}
+
+static int
+urp_add_resolve_parententry (Slapi_PBlock *pb, char *sessionid, Slapi_Entry *entry, Slapi_Entry *parententry, CSN *opcsn)
+{
+ Slapi_DN *parentdn = NULL;
+ Slapi_RDN *add_rdn = NULL;
+ char *newdn = NULL;
+ int ldap_rc;
+ int rc = 0;
+
+ if( is_suffix_entry (pb, entry, &parentdn) )
+ {
+ /* It's OK for the suffix entry's parent to be absent */
+ rc= 0;
+ PROFILE_POINT; /* Add Conflict; Suffix Entry */
+ goto bailout;
+ }
+
+ /* The entry is not a suffix. */
+ if(parententry==NULL) /* The parent entry was not found. */
+ {
+ /* Create a glue entry to stand in for the absent parent */
+ slapi_operation_parameters *op_params;
+ slapi_pblock_get( pb, SLAPI_OPERATION_PARAMETERS, &op_params );
+ ldap_rc = create_glue_entry (pb, sessionid, parentdn, op_params->p.p_add.parentuniqueid, opcsn);
+ if ( LDAP_SUCCESS == ldap_rc )
+ {
+ /* The backend code should now search for the parent again. */
+ rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_EXISTING_DN_ENTRY);
+ rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_PARENT_ENTRY);
+ PROFILE_POINT; /* Add Conflict; Orphaned Entry; Glue Parent */
+ }
+ else
+ {
+ /*
+ * Error. The parent can't be created as a glue entry.
+ * This will cause replication divergence and will
+ * require admin intercession
+ */
+ ldap_rc= LDAP_OPERATIONS_ERROR;
+ slapi_pblock_set(pb, SLAPI_RESULT_CODE, &ldap_rc);
+ rc= -1; /* Abort this Operation */
+ PROFILE_POINT; /* Add Conflict; Orphaned Entry; Impossible to create parent; Refuse Change. */
+ }
+ goto bailout;
+ }
+
+ if(is_tombstone_entry(parententry)) /* The parent is a tombstone */
+ {
+ /* The parent entry must be resurected from the dead. */
+ ldap_rc = tombstone_to_glue (pb, sessionid, parententry, parentdn, REASON_RESURRECT_ENTRY, opcsn);
+ if ( ldap_rc != LDAP_SUCCESS )
+ {
+ ldap_rc= LDAP_OPERATIONS_ERROR;
+ slapi_pblock_set(pb, SLAPI_RESULT_CODE, &ldap_rc);
+ rc = -1; /* Abort the operation */
+ }
+ else
+ {
+ /* The backend add code should now search for the parent again. */
+ rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_EXISTING_DN_ENTRY);
+ rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_PARENT_ENTRY);
+ }
+ PROFILE_POINT; /* Add Conflict; Orphaned Entry; Parent Was Tombstone */
+ goto bailout;
+ }
+
+ /* The parent is healthy */
+ /* Now we need to check that the parent has the correct DN */
+ if (slapi_sdn_isparent(slapi_entry_get_sdn(parententry), slapi_entry_get_sdn(entry)))
+ {
+ rc= 0; /* OK, Add the entry */
+ PROFILE_POINT; /* Add Conflict; Parent Exists */
+ goto bailout;
+ }
+
+ /*
+ * Parent entry doesn't have a DN parent to the entry.
+ * This can happen if parententry was renamed due to
+ * conflict and the child entry was created before
+ * replication occured. See defect 530942.
+ * We need to rename the entry to be child of its parent.
+ */
+ add_rdn = slapi_rdn_new_dn(slapi_entry_get_dn_const (entry));
+ newdn = slapi_dn_plus_rdn(slapi_entry_get_dn_const (parententry), slapi_rdn_get_rdn(add_rdn));
+ slapi_entry_set_dn ( entry,slapi_ch_strdup(newdn));
+ set_pblock_dn (pb,SLAPI_ADD_TARGET,newdn); /* consumes newdn */
+ slapi_log_error ( slapi_log_urp, sessionid,
+ "Parent was renamed. Renamed the child to %s\n", newdn );
+ rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_EXISTING_DN_ENTRY);
+ PROFILE_POINT; /* Add Conflict; Parent Renamed; Rename Operation Entry */
+
+bailout:
+ if (parentdn)
+ slapi_sdn_free(&parentdn);
+ return rc;
+}
+
+/*
+ * urp_annotate_dn:
+ * Returns 0 on failure
+ * Returns > 0 on success (1 on general conflict resolution success, LDAP_NO_SUCH_OBJECT on no-conflict success)
+ *
+ * Use this function to annotate an existing entry only. To annotate
+ * a new entry (the operation entry) see urp_add_operation.
+ */
+static int
+urp_annotate_dn (char *sessionid, Slapi_Entry *entry, CSN *opcsn, const char *optype)
+{
+ int rc = 0; /* Fail */
+ int op_result;
+ char *newrdn;
+ const char *uniqueid;
+ const char *basedn;
+ char ebuf[BUFSIZ];
+
+ uniqueid = slapi_entry_get_uniqueid (entry);
+ basedn = slapi_entry_get_ndn (entry);
+ newrdn = get_rdn_plus_uniqueid ( sessionid, basedn, uniqueid );
+ if(newrdn!=NULL)
+ {
+ mod_namingconflict_attr (uniqueid, basedn, basedn, opcsn);
+ op_result = urp_fixup_rename_entry ( entry, newrdn, 0 );
+ switch(op_result)
+ {
+ case LDAP_SUCCESS:
+ slapi_log_error(slapi_log_urp, sessionid,
+ "Naming conflict %s. Renamed existing entry to %s\n",
+ optype, escape_string (newrdn, ebuf));
+ rc = 1;
+ break;
+ case LDAP_NO_SUCH_OBJECT:
+ /* This means that entry did not really exist!!!
+ * This is clearly indicating that there is a
+ * get_copy_of_entry -> dn2entry returned
+ * an entry (entry) that was already removed
+ * from the ldbm database...
+ * This is bad, because it clearly indicates
+ * some kind of db or cache corruption. We need to print
+ * this fact clearly in the errors log to try
+ * to solve this corruption one day.
+ * However, as far as the conflict is concerned,
+ * this error is completely harmless:
+ * if thew entry did not exist in the first place,
+ * there was never a room
+ * for a conflict!! After fix for 558293, this
+ * state can't be reproduced anymore (5-Oct-01)
+ */
+ slapi_log_error( SLAPI_LOG_FATAL, sessionid,
+ "Entry %s exists in cache but not in DB\n",
+ escape_string (basedn, ebuf) );
+ rc = LDAP_NO_SUCH_OBJECT;
+ break;
+ default:
+ slapi_log_error( slapi_log_urp, sessionid,
+ "Failed to annotate %s, err=%d\n", newrdn, op_result);
+ }
+ slapi_ch_free ( (void**)&newrdn );
+ }
+ return rc;
+}
+
+/*
+ * An URP Naming Collision helper function. Retreives a list of entries
+ * that have the given dn excluding the unique id of the entry. Any
+ * entries returned will be entries that have been added with the same
+ * dn, but caused a naming conflict when replicated. The URP to fix
+ * this constraint violation is to append the unique id of the entry
+ * to its RDN.
+ */
+static Slapi_Entry *
+urp_get_min_naming_conflict_entry ( Slapi_PBlock *pb, char *sessionid, CSN *opcsn )
+{
+ Slapi_PBlock *newpb = NULL;
+ LDAPControl **server_ctrls = NULL;
+ Slapi_Entry **entries = NULL;
+ Slapi_Entry *min_naming_conflict_entry = NULL;
+ const CSN *min_csn = NULL;
+ char *filter = NULL;
+ char *parent_dn = NULL;
+ char *basedn;
+ int i = 0;
+ int min_i = -1;
+ int op_result = LDAP_SUCCESS;
+
+ slapi_pblock_get (pb, SLAPI_URP_NAMING_COLLISION_DN, &basedn);
+ if (NULL == basedn || strncmp (basedn, SLAPI_ATTR_UNIQUEID, strlen(SLAPI_ATTR_UNIQUEID)) == 0)
+ return NULL;
+
+ slapi_log_error ( SLAPI_LOG_REPL, sessionid,
+ "Enter urp_get_min_naming_conflict_entry for %s\n", basedn);
+
+ filter = slapi_ch_malloc(50 + strlen(basedn));
+ sprintf(filter, "(%s=%s %s)", ATTR_NSDS5_REPLCONFLICT, REASON_ANNOTATE_DN, basedn);
+
+ /* server_ctrls will be freed when newpb is destroyed */
+ server_ctrls = (LDAPControl **)slapi_ch_calloc (2, sizeof (LDAPControl *));
+ server_ctrls[0] = create_managedsait_control();
+ server_ctrls[1] = NULL;
+
+ newpb = slapi_pblock_new();
+ parent_dn = slapi_dn_parent (basedn);
+ slapi_search_internal_set_pb(newpb,
+ parent_dn, /* Base DN */
+ LDAP_SCOPE_ONELEVEL,
+ filter,
+ NULL, /* Attrs */
+ 0, /* AttrOnly */
+ server_ctrls, /* Controls */
+ NULL, /* UniqueID */
+ repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION),
+ 0);
+ slapi_search_internal_pb(newpb);
+ slapi_pblock_get(newpb, SLAPI_PLUGIN_INTOP_RESULT, &op_result);
+ slapi_pblock_get(newpb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries);
+ if ( (op_result != LDAP_SUCCESS) || (entries == NULL) )
+ {
+ /* Log a message */
+ goto done;
+ }
+ /* For all entries, get the one with the smallest dn csn */
+ for (i = 0; NULL != entries[i]; i++)
+ {
+ const CSN *dncsn;
+ dncsn = entry_get_dncsn(entries[i]);
+ if ((dncsn != opcsn) &&
+ ((min_csn == NULL) || (csn_compare(dncsn, min_csn) < 0)) &&
+ !is_tombstone_entry (entries[i]))
+ {
+ min_csn = dncsn;
+ min_i = i;
+ }
+ /*
+ * If there are too many conflicts, the current urp code has no
+ * guarantee for all servers to converge anyway, because the
+ * urp and the backend can't be done in one transaction due
+ * to either performance or the deadlock problem.
+ * Don't sacrifice the performance too much for impossible.
+ */
+ if (min_csn && i > 5)
+ {
+ break;
+ }
+ }
+
+ if (min_csn != NULL) {
+ /* Found one entry */
+ min_naming_conflict_entry = slapi_entry_dup(entries[min_i]);
+ }
+
+done:
+ slapi_ch_free((void **)&parent_dn);
+ slapi_ch_free((void **)&filter);
+ slapi_free_search_results_internal(newpb);
+ slapi_pblock_destroy(newpb);
+ newpb = NULL;
+
+ slapi_log_error ( SLAPI_LOG_REPL, sessionid,
+ "Leave urp_get_min_naming_conflict_entry (found %d entries)\n", i);
+
+ return min_naming_conflict_entry;
+}
+
+/*
+ * If an entry is deleted or renamed, a new winner may be
+ * chosen from its naming competitors.
+ * The entry with the smallest dncsn restores its original DN.
+ */
+static int
+urp_naming_conflict_removal ( Slapi_PBlock *pb, char *sessionid, CSN *opcsn, const char *optype )
+{
+ Slapi_Entry *min_naming_conflict_entry;
+ Slapi_RDN *oldrdn, *newrdn;
+ const char *oldrdnstr, *newrdnstr;
+ int op_result;
+
+ /*
+ * Backend op has set SLAPI_URP_NAMING_COLLISION_DN to the basedn.
+ */
+ min_naming_conflict_entry = urp_get_min_naming_conflict_entry (pb, sessionid, opcsn);
+ if (min_naming_conflict_entry == NULL)
+ {
+ return 0;
+ }
+
+ /* Step 1: Restore the entry's original DN */
+
+ oldrdn = slapi_rdn_new_sdn ( slapi_entry_get_sdn (min_naming_conflict_entry) );
+ oldrdnstr = slapi_rdn_get_rdn ( oldrdn );
+
+ /* newrdnstr is the old rdn of the entry minus the nsuniqueid part */
+ newrdn = slapi_rdn_new_rdn ( oldrdn );
+ slapi_rdn_remove_attr (newrdn, SLAPI_ATTR_UNIQUEID );
+ newrdnstr = slapi_rdn_get_rdn ( newrdn );
+
+ /*
+ * Set OP_FLAG_ACTION_INVOKE_FOR_REPLOP since this operation
+ * is done after DB lock was released. The backend modrdn
+ * will acquire the DB lock if it sees this flag.
+ */
+ op_result = urp_fixup_rename_entry (min_naming_conflict_entry, newrdnstr, OP_FLAG_ACTION_INVOKE_FOR_REPLOP);
+ if ( op_result != LDAP_SUCCESS )
+ {
+ slapi_log_error (slapi_log_urp, sessionid,
+ "Failed to restore RDN of %s, err=%d\n", oldrdnstr, op_result);
+ goto bailout;
+ }
+ slapi_log_error (slapi_log_urp, sessionid,
+ "Naming conflict removed by %s. RDN of %s was restored\n", optype, oldrdnstr);
+
+ /* Step2: Remove ATTR_NSDS5_REPLCONFLICT from the winning entry */
+ /*
+ * A fixup op will not invoke urp_modrdn_operation(). Even it does,
+ * urp_modrdn_operation() will do nothing because of the same CSN.
+ */
+ op_result = del_replconflict_attr (min_naming_conflict_entry, opcsn, OP_FLAG_ACTION_INVOKE_FOR_REPLOP);
+ if (op_result != LDAP_SUCCESS) {
+ slapi_log_error(SLAPI_LOG_REPL, sessionid,
+ "Failed to remove nsds5ReplConflict for %s, err=%d\n",
+ newrdnstr, op_result);
+ }
+
+bailout:
+ slapi_entry_free (min_naming_conflict_entry);
+ slapi_rdn_free(&oldrdn);
+ slapi_rdn_free(&newrdn);
+ return op_result;
+}
+
+/* The returned value is either null or "uniqueid=<uniqueid>+<basedn>" */
+static char *
+get_dn_plus_uniqueid(char *sessionid, const char *olddn, const char *uniqueid)
+{
+ Slapi_DN *sdn= slapi_sdn_new_dn_byval(olddn);
+ Slapi_RDN *rdn= slapi_rdn_new();
+ char *newdn;
+
+ PR_ASSERT(uniqueid!=NULL);
+
+ /* Check if the RDN already contains the Unique ID */
+ slapi_sdn_get_rdn(sdn,rdn);
+ if(slapi_rdn_contains(rdn,SLAPI_ATTR_UNIQUEID,uniqueid,strlen(uniqueid)))
+ {
+ /* The Unique ID is already in the RDN.
+ * This is a highly improbable collision.
+ * It suggests that a duplicate UUID was generated.
+ * This will cause replication divergence and will
+ * require admin intercession
+ */
+ slapi_log_error(SLAPI_LOG_FATAL, sessionid,
+ "Annotated DN %s has naming conflict\n", olddn );
+ newdn= NULL;
+ }
+ else
+ {
+ slapi_rdn_add(rdn,SLAPI_ATTR_UNIQUEID,uniqueid);
+ slapi_sdn_set_rdn(sdn, rdn);
+ newdn= slapi_ch_strdup(slapi_sdn_get_dn(sdn));
+ }
+ slapi_sdn_free(&sdn);
+ slapi_rdn_free(&rdn);
+ return newdn;
+}
+
+static char *
+get_rdn_plus_uniqueid(char *sessionid, const char *olddn, const char *uniqueid)
+{
+ char *newrdn;
+ /* Check if the RDN already contains the Unique ID */
+ Slapi_DN *sdn= slapi_sdn_new_dn_byval(olddn);
+ Slapi_RDN *rdn= slapi_rdn_new();
+ slapi_sdn_get_rdn(sdn,rdn);
+ PR_ASSERT(uniqueid!=NULL);
+ if(slapi_rdn_contains(rdn,SLAPI_ATTR_UNIQUEID,uniqueid,strlen(uniqueid)))
+ {
+ /* The Unique ID is already in the RDN.
+ * This is a highly improbable collision.
+ * It suggests that a duplicate UUID was generated.
+ * This will cause replication divergence and will
+ * require admin intercession
+ */
+ slapi_log_error(SLAPI_LOG_FATAL, sessionid,
+ "Annotated DN %s has naming conflict\n", olddn );
+ newrdn= NULL;
+ }
+ else
+ {
+ slapi_rdn_add(rdn,SLAPI_ATTR_UNIQUEID,uniqueid);
+ newrdn= slapi_ch_strdup(slapi_rdn_get_rdn(rdn));
+ }
+ slapi_sdn_free(&sdn);
+ slapi_rdn_free(&rdn);
+ return newrdn;
+}
+
+static void
+set_pblock_dn (Slapi_PBlock* pb,int pblock_parameter,char *newdn)
+{
+ char *olddn;
+ slapi_pblock_get( pb, pblock_parameter, &olddn );
+ slapi_ch_free((void**)&olddn);
+ slapi_pblock_set( pb, pblock_parameter, newdn );
+}
+
+static int
+is_suffix_entry ( Slapi_PBlock *pb, Slapi_Entry *entry, Slapi_DN **parentdn )
+{
+ return is_suffix_dn ( pb, slapi_entry_get_sdn(entry), parentdn );
+}
+
+int
+is_suffix_dn ( Slapi_PBlock *pb, const Slapi_DN *dn, Slapi_DN **parentdn )
+{
+ Slapi_Backend *backend;
+ int rc;
+
+ *parentdn = slapi_sdn_new();
+ slapi_pblock_get( pb, SLAPI_BACKEND, &backend );
+ slapi_sdn_get_backend_parent (dn, *parentdn, backend);
+
+ /* A suffix entry doesn't have parent dn */
+ rc = slapi_sdn_isempty (*parentdn) ? 1 : 0;
+
+ return rc;
+}
+
+static int
+mod_namingconflict_attr (const char *uniqueid, const char *entrydn, const char *conflictdn, CSN *opcsn)
+{
+ Slapi_Mods smods;
+ char buf[BUFSIZ];
+ int op_result;
+
+ sprintf (buf, "%s %s", REASON_ANNOTATE_DN, conflictdn);
+ slapi_mods_init (&smods, 2);
+ if ( strncmp (entrydn, SLAPI_ATTR_UNIQUEID, strlen(SLAPI_ATTR_UNIQUEID)) != 0 )
+ {
+ slapi_mods_add (&smods, LDAP_MOD_ADD, ATTR_NSDS5_REPLCONFLICT, strlen(buf), buf);
+ }
+ else
+ {
+ /*
+ * If the existing entry is already a naming conflict loser,
+ * the following replace operation should result in the
+ * replace of the ATTR_NSDS5_REPLCONFLICT index as well.
+ */
+ slapi_mods_add (&smods, LDAP_MOD_REPLACE, ATTR_NSDS5_REPLCONFLICT, strlen(buf), buf);
+ }
+ op_result = urp_fixup_modify_entry (uniqueid, entrydn, opcsn, &smods, 0);
+ slapi_mods_done (&smods);
+ return op_result;
+}
+
+static int
+del_replconflict_attr (Slapi_Entry *entry, CSN *opcsn, int opflags)
+{
+ Slapi_Attr *attr;
+ int op_result = 0;
+
+ if (slapi_entry_attr_find (entry, ATTR_NSDS5_REPLCONFLICT, &attr) == 0)
+ {
+ Slapi_Mods smods;
+ const char *uniqueid;
+ const char *entrydn;
+
+ uniqueid = slapi_entry_get_uniqueid (entry);
+ entrydn = slapi_entry_get_dn_const (entry);
+ slapi_mods_init (&smods, 2);
+ slapi_mods_add (&smods, LDAP_MOD_DELETE, ATTR_NSDS5_REPLCONFLICT, 0, NULL);
+ op_result = urp_fixup_modify_entry (uniqueid, entrydn, opcsn, &smods, opflags);
+ slapi_mods_done (&smods);
+ }
+ return op_result;
+}
diff --git a/ldap/servers/plugins/replication/urp.h b/ldap/servers/plugins/replication/urp.h
new file mode 100644
index 00000000..9db477bd
--- /dev/null
+++ b/ldap/servers/plugins/replication/urp.h
@@ -0,0 +1,45 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/*
+ */
+
+#define REASON_ANNOTATE_DN "namingConflict"
+#define REASON_RESURRECT_ENTRY "deletedEntryHasChildren"
+
+/*
+ * urp.c
+ */
+int urp_modify_operation( Slapi_PBlock *pb );
+int urp_add_operation( Slapi_PBlock *pb );
+int urp_delete_operation( Slapi_PBlock *pb );
+int urp_post_delete_operation( Slapi_PBlock *pb );
+int urp_modrdn_operation( Slapi_PBlock *pb );
+int urp_post_modrdn_operation( Slapi_PBlock *pb );
+
+/* urp internal ops */
+int urp_fixup_add_entry (Slapi_Entry *e, const char *target_uniqueid, const char *parentuniqueid, CSN *opcsn, int opflags);
+int urp_fixup_delete_entry (const char *uniqueid, const char *dn, CSN *opcsn, int opflags);
+int urp_fixup_rename_entry (Slapi_Entry *entry, const char *newrdn, int opflags);
+int urp_fixup_modify_entry (const char *uniqueid, const char *dn, CSN *opcsn, Slapi_Mods *smods, int opflags);
+
+int is_suffix_dn (Slapi_PBlock *pb, const Slapi_DN *dn, Slapi_DN **parenddn);
+
+/*
+ * urp_glue.c
+ */
+int is_glue_entry(const Slapi_Entry* entry);
+int create_glue_entry ( Slapi_PBlock *pb, char *sessionid, Slapi_DN *dn, const char *uniqueid, CSN *opcsn );
+int entry_to_glue(char *sessionid, const Slapi_Entry* entry, const char *reason, CSN *opcsn);
+int glue_to_entry (Slapi_PBlock *pb, Slapi_Entry *entry );
+PRBool get_glue_csn(const Slapi_Entry *entry, const CSN **gluecsn);
+
+/*
+ * urp_tombstone.c
+ */
+int is_tombstone_entry(const Slapi_Entry* entry);
+int tombstone_to_glue(Slapi_PBlock *pb, const char *sessionid, Slapi_Entry *entry, const Slapi_DN *parentdn, const char *reason, CSN *opcsn);
+int entry_to_tombstone ( Slapi_PBlock *pb, Slapi_Entry *entry );
+PRBool get_tombstone_csn(const Slapi_Entry *entry, const CSN **delcsn);
diff --git a/ldap/servers/plugins/replication/urp_glue.c b/ldap/servers/plugins/replication/urp_glue.c
new file mode 100644
index 00000000..dcb2f72d
--- /dev/null
+++ b/ldap/servers/plugins/replication/urp_glue.c
@@ -0,0 +1,235 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+/*
+ * urp_glue.c - Update Resolution Procedures - Glue
+ */
+
+#include "slapi-plugin.h"
+#include "repl5.h"
+#include "urp.h"
+
+
+#define RDNBUFSIZE 2048
+extern int slapi_log_urp;
+
+/*
+ * Check if the entry is glue.
+ */
+int
+is_glue_entry(const Slapi_Entry* entry)
+{
+ /* JCMREPL - Is there a more efficient way to do this? */
+ return slapi_entry_attr_hasvalue(entry, SLAPI_ATTR_OBJECTCLASS, "glue");
+}
+
+/* returns PR_TRUE if the entry is a glue entry, PR_FALSE otherwise
+ sets the gluecsn if it is a glue entry - gluecsn may (but should not) be NULL */
+PRBool
+get_glue_csn(const Slapi_Entry *entry, const CSN **gluecsn)
+{
+ PRBool isglue = PR_FALSE;
+ Slapi_Attr *oc_attr = NULL;
+
+ /* cast away const - entry */
+ if (entry_attr_find_wsi((Slapi_Entry*)entry, SLAPI_ATTR_OBJECTCLASS, &oc_attr) == ATTRIBUTE_PRESENT)
+ {
+ Slapi_Value *glue_value = NULL;
+ struct berval v;
+ v.bv_val = "glue";
+ v.bv_len = strlen(v.bv_val);
+ if (attr_value_find_wsi(oc_attr, &v, &glue_value) == VALUE_PRESENT)
+ {
+ isglue = PR_TRUE;
+ *gluecsn = value_get_csn(glue_value, CSN_TYPE_VALUE_UPDATED);
+ }
+ }
+
+ return isglue;
+}
+
+/*
+ * Submit a Modify operation to turn the Entry into Glue.
+ */
+int
+entry_to_glue(char *sessionid, const Slapi_Entry* entry, const char *reason, CSN *opcsn)
+{
+ int op_result = 0;
+ const char *dn;
+ char ebuf[BUFSIZ];
+ slapi_mods smods;
+ Slapi_Attr *attr;
+
+ dn = slapi_entry_get_dn_const (entry);
+ slapi_mods_init(&smods, 4);
+ /*
+ richm: sometimes the entry is already a glue entry (how did that happen?)
+ OR
+ the entry is already objectclass extensibleObject or already has the
+ conflict attribute and/or value
+ */
+ if (!slapi_entry_attr_hasvalue(entry, SLAPI_ATTR_OBJECTCLASS, "glue"))
+ {
+ slapi_mods_add_string( &smods, LDAP_MOD_ADD, SLAPI_ATTR_OBJECTCLASS, "glue" );
+
+ if (!slapi_entry_attr_hasvalue(entry, SLAPI_ATTR_OBJECTCLASS, "extensibleobject"))
+ slapi_mods_add_string( &smods, LDAP_MOD_ADD, SLAPI_ATTR_OBJECTCLASS, "extensibleobject" );
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: Target entry %s is already a glue entry reason %s\n",
+ sessionid, escape_string(dn, ebuf), reason);
+ }
+
+ if (slapi_entry_attr_find (entry, ATTR_NSDS5_REPLCONFLICT, &attr) == 0)
+ {
+ slapi_mods_add_string( &smods, LDAP_MOD_REPLACE, ATTR_NSDS5_REPLCONFLICT, reason);
+ }
+ else
+ {
+ slapi_mods_add_string( &smods, LDAP_MOD_ADD, ATTR_NSDS5_REPLCONFLICT, reason);
+ }
+
+ if (slapi_mods_get_num_mods(&smods) > 0)
+ {
+ op_result = urp_fixup_modify_entry (NULL, dn, opcsn, &smods, 0);
+ if (op_result == LDAP_SUCCESS)
+ {
+ slapi_log_error (slapi_log_urp, repl_plugin_name,
+ "%s: Turned the entry %s to glue, reason %s\n",
+ sessionid, escape_string(dn, ebuf), reason);
+ }
+ }
+
+ slapi_mods_done(&smods);
+ return op_result;
+}
+
+static const char *glue_entry =
+ "dn: %s\n"
+ "%s"
+ "objectclass: top\n"
+ "objectclass: extensibleObject\n" /* JCMREPL - To avoid schema checking. */
+ "objectclass: glue\n"
+ "nsuniqueid: %s\n"
+ "%s: %s\n"; /* Add why it's been created */
+
+static int
+do_create_glue_entry(const Slapi_RDN *rdn, const Slapi_DN *superiordn, const char *uniqueid, const char *reason, CSN *opcsn)
+{
+ int op_result= LDAP_OPERATIONS_ERROR;
+ int rdnval_index = 0;
+ int rdntype_len, rdnval_len, rdnpair_len, rdnstr_len, alloc_len;
+ Slapi_Entry *e;
+ Slapi_DN *sdn = NULL;
+ Slapi_RDN *newrdn = slapi_rdn_new_rdn(rdn);
+ char *estr, *rdnstr, *rdntype, *rdnval, *rdnpair;
+ sdn = slapi_sdn_new_dn_byval(slapi_sdn_get_ndn(superiordn));
+ slapi_sdn_add_rdn(sdn,rdn);
+
+
+ /* must take care of multi-valued rdn: split rdn into different lines introducing
+ * '\n' between each type/value pair.
+ */
+ alloc_len = RDNBUFSIZE;
+ rdnstr = slapi_ch_malloc(alloc_len);
+ rdnpair = rdnstr;
+ *rdnpair = '\0'; /* so that strlen(rdnstr) may return 0 the first time it's called */
+ while ((rdnval_index = slapi_rdn_get_next(newrdn, rdnval_index, &rdntype, &rdnval)) != -1) {
+ rdntype_len = strlen(rdntype);
+ rdnval_len = strlen(rdnval);
+ rdnpair_len = LDIF_SIZE_NEEDED(rdntype_len, rdnval_len);
+ rdnstr_len = strlen(rdnstr);
+ if ((rdnstr_len + rdnpair_len + 1) > alloc_len) {
+ alloc_len += (rdnpair_len + 1);
+ rdnstr = slapi_ch_realloc(rdnstr, alloc_len);
+ rdnpair = &rdnstr[rdnstr_len];
+ }
+ ldif_put_type_and_value_with_options(&rdnpair, rdntype,
+ rdnval, rdnval_len, LDIF_OPT_NOWRAP);
+ *rdnpair = '\0';
+ }
+ estr= slapi_ch_malloc(strlen(glue_entry) + slapi_sdn_get_ndn_len(sdn) +
+ strlen(rdnstr) + strlen(uniqueid) +
+ strlen(ATTR_NSDS5_REPLCONFLICT) + strlen(reason) + 1);
+ sprintf(estr, glue_entry, slapi_sdn_get_ndn(sdn), rdnstr, uniqueid,
+ ATTR_NSDS5_REPLCONFLICT, reason);
+ slapi_ch_free((void**)&rdnstr);
+ slapi_rdn_done(newrdn);
+ slapi_ch_free((void**)&newrdn);
+ e = slapi_str2entry( estr, 0 );
+ PR_ASSERT(e!=NULL);
+ if ( e!=NULL )
+ {
+ slapi_entry_set_uniqueid (e, slapi_ch_strdup(uniqueid));
+ op_result = urp_fixup_add_entry (e, NULL, NULL, opcsn, 0);
+ slapi_ch_free ( (void **) &estr ); /* XXXggood - this leaks if e == NULL */
+ }
+ slapi_sdn_free(&sdn);
+ return op_result;
+}
+
+int
+create_glue_entry ( Slapi_PBlock *pb, char *sessionid, Slapi_DN *dn, const char *uniqueid, CSN *opcsn )
+{
+ int op_result;
+ const char *dnstr;
+
+ if ( slapi_sdn_get_dn (dn) )
+ dnstr = slapi_sdn_get_dn (dn);
+ else
+ dnstr = "";
+
+ if ( NULL == uniqueid )
+ {
+ op_result = LDAP_OPERATIONS_ERROR;
+ slapi_log_error (SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Can't create glue %s, uniqueid=NULL\n", sessionid, dnstr);
+ }
+ else
+ {
+ Slapi_Backend *backend;
+ Slapi_DN *superiordn = slapi_sdn_new();
+ Slapi_RDN *rdn= slapi_rdn_new();
+ int done= 0;
+
+ slapi_pblock_get( pb, SLAPI_BACKEND, &backend );
+ slapi_sdn_get_backend_parent ( dn, superiordn, backend );
+ slapi_sdn_get_rdn ( dn, rdn );
+
+ while(!done)
+ {
+ op_result= do_create_glue_entry(rdn, superiordn, uniqueid, "missingEntry", opcsn);
+ switch(op_result)
+ {
+ case LDAP_SUCCESS:
+ slapi_log_error ( SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Created glue entry %s uniqueid=%s reason missingEntry\n",
+ sessionid, dnstr, uniqueid);
+ done= 1;
+ break;
+ case LDAP_NO_SUCH_OBJECT:
+ /* The parent is missing */
+ {
+ /* JCMREPL - Create the parent ... recursion?... but what's the uniqueid? */
+ PR_ASSERT(0); /* JCMREPL */
+ }
+ default:
+ slapi_log_error ( SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Can't created glue entry %s uniqueid=%s, error %d\n",
+ sessionid, dnstr, uniqueid, op_result);
+ break;
+ }
+ /* JCMREPL - Could get trapped in this loop forever! */
+ }
+
+ slapi_rdn_free ( &rdn );
+ slapi_sdn_free ( &superiordn );
+ }
+
+ return op_result;
+}
diff --git a/ldap/servers/plugins/replication/urp_tombstone.c b/ldap/servers/plugins/replication/urp_tombstone.c
new file mode 100644
index 00000000..3b24b928
--- /dev/null
+++ b/ldap/servers/plugins/replication/urp_tombstone.c
@@ -0,0 +1,210 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+/*
+ * urp_tombstone.c - Update Resolution Procedures - Tombstones
+ */
+
+#include "slapi-plugin.h"
+#include "repl5.h"
+#include "urp.h"
+
+extern int slapi_log_urp;
+
+/*
+ * Check if the entry is a tombstone.
+ */
+int
+is_tombstone_entry(const Slapi_Entry* entry)
+{
+ int flag;
+
+ /* LP: This doesn't work very well with entries that we tombstone ourself */
+ flag = slapi_entry_flag_is_set (entry, SLAPI_ENTRY_FLAG_TOMBSTONE);
+ if (flag == 0)
+ {
+ /* This is slow */
+ flag = slapi_entry_attr_hasvalue(entry, SLAPI_ATTR_OBJECTCLASS, SLAPI_ATTR_VALUE_TOMBSTONE);
+ }
+ return flag;
+}
+
+PRBool
+get_tombstone_csn(const Slapi_Entry *entry, const CSN **delcsn)
+{
+ PRBool ists = PR_FALSE;
+ if (is_tombstone_entry(entry)) {
+ ists = PR_TRUE;
+ *delcsn = _get_deletion_csn((Slapi_Entry *)entry); /* cast away const */
+ }
+
+ return ists;
+}
+
+static int
+tombstone_to_glue_resolve_parent (
+ Slapi_PBlock *pb,
+ const char *sessionid,
+ const Slapi_DN *parentdn,
+ const char *parentuniqueid,
+ CSN *opcsn)
+{
+ /* Let's have a look at the parent of this entry... */
+ if(!slapi_sdn_isempty(parentdn) && parentuniqueid!=NULL)
+ {
+ int op_result;
+ Slapi_PBlock *newpb= slapi_pblock_new();
+ slapi_search_internal_set_pb(
+ newpb,
+ slapi_sdn_get_dn(parentdn), /* JCM - This DN just identifies the backend to be searched. */
+ LDAP_SCOPE_BASE,
+ "objectclass=*",
+ NULL, /*attrs*/
+ 0, /*attrsonly*/
+ NULL, /*Controls*/
+ parentuniqueid, /*uniqueid*/
+ repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION),
+ 0);
+ slapi_search_internal_pb(newpb);
+ slapi_pblock_get(newpb, SLAPI_PLUGIN_INTOP_RESULT, &op_result);
+ switch(op_result)
+ {
+ case LDAP_SUCCESS:
+ {
+ Slapi_Entry **entries= NULL;
+ /* OK, the tombstone entry parent exists. Is it also a tombstone? */
+ slapi_pblock_get(newpb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries);
+ if(entries!=NULL && entries[0]!=NULL)
+ {
+ if(is_tombstone_entry(entries[0]))
+ {
+ tombstone_to_glue (pb, sessionid, entries[0], parentdn, REASON_RESURRECT_ENTRY, opcsn);
+ }
+ }
+ else
+ {
+ /* JCM - Couldn't find the entry! */
+ }
+ }
+ break;
+ default:
+ /* So, the tombstone entry had a parent... but it's gone. */
+ /* That's probably a bad thing. */
+ break;
+ }
+ slapi_free_search_results_internal (newpb);
+ slapi_pblock_destroy(newpb);
+ }
+ return 0;
+}
+
+/*
+ * Convert a tombstone into a glue entry.
+ */
+int
+tombstone_to_glue (
+ Slapi_PBlock *pb,
+ const char *sessionid,
+ Slapi_Entry *tombstoneentry,
+ const Slapi_DN *tombstonedn,
+ const char *reason,
+ CSN *opcsn)
+{
+ Slapi_DN *parentdn;
+ char *parentuniqueid;
+ const char *tombstoneuniqueid;
+ Slapi_Entry *addingentry;
+ const char *addingdn;
+ int op_result;
+
+ /* JCMREPL
+ * Nothing logged to the 5.0 Change Log
+ * Add is logged to the 4.0 Change Log - Core server Add code
+ * must attach the entry to the Operation
+ */
+
+
+ /* Resurrect the parent entry first */
+
+ /* JCM - This DN calculation is odd. It could resolve to NULL
+ * which won't help us identify the correct backend to search.
+ */
+ is_suffix_dn (pb, tombstonedn, &parentdn);
+ parentuniqueid= slapi_entry_attr_get_charptr (tombstoneentry,
+ SLAPI_ATTR_VALUE_PARENT_UNIQUEID); /* Allocated */
+ tombstone_to_glue_resolve_parent (pb, sessionid, parentdn, parentuniqueid, opcsn);
+ slapi_sdn_free(&parentdn);
+
+ /* Submit an Add operation to turn the tombstone entry into glue. */
+ /*
+ * The tombstone is stored with an invalid DN, we must fix this.
+ */
+ addingentry = slapi_entry_dup(tombstoneentry);
+ addingdn = slapi_sdn_get_dn(tombstonedn);
+ slapi_entry_set_dn(addingentry,slapi_ch_strdup(addingdn)); /* consumes DN */
+
+ if (!slapi_entry_attr_hasvalue(addingentry, ATTR_NSDS5_REPLCONFLICT, reason))
+ {
+ /* Add the reason of turning it to glue - The backend code will use it*/
+ slapi_entry_add_string(addingentry, ATTR_NSDS5_REPLCONFLICT, reason);
+ }
+ tombstoneuniqueid= slapi_entry_get_uniqueid(tombstoneentry);
+ op_result = urp_fixup_add_entry (addingentry, tombstoneuniqueid, parentuniqueid, opcsn, OP_FLAG_RESURECT_ENTRY);
+ if (op_result == LDAP_SUCCESS)
+ {
+ slapi_log_error (slapi_log_urp, repl_plugin_name,
+ "%s: Resurrected tombstone %s to glue reason '%s'\n", sessionid, addingdn, reason);
+ }
+ else
+ {
+ slapi_log_error (SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Can't resurrect tombstone %s to glue reason '%s', error=%d\n",
+ sessionid, addingdn, reason, op_result);
+ }
+ slapi_entry_free (addingentry);
+ return op_result;
+}
+
+int
+entry_to_tombstone ( Slapi_PBlock *pb, Slapi_Entry *entry )
+{
+ Slapi_Operation *op;
+ Slapi_Mods smods;
+ CSN *opcsn;
+ const char *uniqueid;
+ int op_result = LDAP_SUCCESS;
+
+ slapi_pblock_get ( pb, SLAPI_OPERATION, &op );
+ opcsn = operation_get_csn ( op );
+ uniqueid = slapi_entry_get_uniqueid ( entry );
+
+
+ slapi_mods_init ( &smods, 2 );
+ /* Remove objectclass=glue */
+ slapi_mods_add ( &smods, LDAP_MOD_DELETE, SLAPI_ATTR_OBJECTCLASS, strlen("glue"), "glue");
+ /* Remove any URP conflict since a tombstone shouldn't
+ * be retrieved later for conflict removal.
+ */
+ slapi_mods_add ( &smods, LDAP_MOD_DELETE, ATTR_NSDS5_REPLCONFLICT, 0, NULL );
+
+ op_result = urp_fixup_modify_entry (uniqueid, slapi_entry_get_dn_const (entry), opcsn, &smods, 0);
+ slapi_mods_done ( &smods );
+
+ /*
+ * Delete the entry.
+ */
+ if ( op_result == LDAP_SUCCESS )
+ {
+ /*
+ * Using internal delete operation since it would go
+ * through the urp operations and trigger the recursive
+ * fixup if applicable.
+ */
+ op_result = urp_fixup_delete_entry (uniqueid, slapi_entry_get_dn_const (entry), opcsn, 0);
+ }
+
+ return op_result;
+}
diff --git a/ldap/servers/plugins/retrocl/Makefile b/ldap/servers/plugins/retrocl/Makefile
new file mode 100644
index 00000000..c81f0dfe
--- /dev/null
+++ b/ldap/servers/plugins/retrocl/Makefile
@@ -0,0 +1,135 @@
+#
+# BEGIN COPYRIGHT BLOCK
+# Copyright 2001 Sun Microsystems, Inc.
+# Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+# All rights reserved.
+# END COPYRIGHT BLOCK
+#
+#
+# GNU Makefile for Directory Server "Retrocl" plugin
+
+LDAP_SRC = ../../..
+MCOM_ROOT = ../../../../..
+
+NOSTDCLEAN=true # don't let nsconfig.mk define target clean
+NOSTDSTRIP=true # don't let nsconfig.mk define target strip
+NSPR20=true # probably should be defined somewhere else (not sure where)
+
+OBJDEST = $(OBJDIR)/lib/retrocl-plugin
+BINDIR = $(LDAP_SERVER_RELDIR)
+LIBDIR = $(LIB_RELDIR)
+
+include $(MCOM_ROOT)/ldapserver/nsdefs.mk
+include $(MCOM_ROOT)/ldapserver/nsconfig.mk
+include $(LDAP_SRC)/nsldap.mk
+include $(MCOM_ROOT)/ldapserver/ns_usedb.mk
+INCLUDES+=-I$(DB_INCLUDE)
+
+ifeq ($(ARCH), WINNT)
+DEF_FILE:=./retrocl.def
+endif
+
+CFLAGS += $(SLCFLAGS) -DSLAPD_LOGGING
+
+ifeq ($(ARCH), WINNT)
+CFLAGS += /WX
+endif
+
+ifdef TEST_CL5
+CFLAGS += -DTEST_CL5
+endif
+
+INCLUDES += -I$(LDAP_SRC)/servers/slapd -I$(DB_INCLUDE)
+
+ifeq ($(ARCH), WINNT)
+SUBSYSTEM=console
+endif
+
+LOCAL_OBJS= \
+ retrocl.o \
+ retrocl_po.o \
+ retrocl_rootdse.o \
+ retrocl_cn.o \
+ retrocl_trim.o \
+ retrocl_create.o \
+
+
+
+LIBRETROCL_OBJS = $(addprefix $(OBJDEST)/, $(LOCAL_OBJS))
+
+ifeq ($(ARCH), WINNT)
+RETROCL_DLL_OBJ = $(addprefix $(OBJDEST)/, dllmain.o)
+endif
+
+LIBRETROCL= $(addprefix $(LIBDIR)/, $(RETROCL_DLL).$(DLL_SUFFIX))
+
+LT_OBJS = $(addprefix $(OBJDEST)/, linktest.o)
+
+#EXTRA_LIBS_DEP = $(LDAPSDK_DEP) \
+# $(LDAP_LIBLDIF_DEP) \
+# $(LDAP_SLIBLCACHE_DEP) $(DB_LIB_DEP) $(LIBSLAPD_DEP) \
+# $(LDAP_COMMON_LIBS_DEP)
+
+#EXTRA_LIBS = $(LIBACCESS) $(LDAP_SDK_LIBSSLDAP_LIB) $(ADMINUTIL_LINK) \
+# $(LDAP_SDK_LIBLDAP_DLL) $(LDAP_SLIBLCACHE) $(DB_LIB) \
+# $(PLATFORM_SPECIFIC_EXTRA_LIBRARY) $(LIBSLAPD) $(LDAP_LIBLITEKEY) \
+# $(NLSLINK) $(ALIBS) \
+# $(LDAP_SDK_LIBSSLDAP_LIB) $(LDAP_SDK_LIBLDAP_DLL) \
+# $(LIBSECURITYLINK) $(NSPRLINK) $(DBMLINK) \
+# $(THREADSLIB) $(LDAP_COMMON_LIBS) $(NSPRLINK) $(SVRCORELINK)
+
+ifeq ($(ARCH), WINNT)
+EXTRA_LIBS_DEP += $(LIBSLAPD_DEP)
+EXTRA_LIBS_DEP += $(LDAPSDK_DEP) $(DB_LIB_DEP) $(NSPR_DEP)
+EXTRA_LIBS += $(LIBSLAPD) $(LDAP_SDK_LIBLDAP_DLL) $(DB_LIB)
+endif
+
+ifeq ($(ARCH), AIX)
+EXTRA_LIBS_DEP += $(LIBSLAPD_DEP)
+EXTRA_LIBS_DEP += $(LDAPSDK_DEP) $(DB_LIB_DEP) $(NSPR_DEP)
+EXTRA_LIBS += $(LIBSLAPD) $(LDAP_SDK_LIBLDAP_DLL) $(DB_LIB)
+endif
+
+ifeq ($(ARCH), HPUX)
+EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAPSDK_DEP) $(NSPR_DEP) $(SECURITY_DEP)
+EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(SECURITYLINK) $(NSPRLINK) $(ICULINK)
+endif
+
+ifeq ($(ARCH), WINNT)
+DLL_LDFLAGS += -def:"./retrocl.def"
+endif # WINNT
+
+ifeq ($(ARCH), AIX)
+EXTRA_LIBS += $(DLL_EXTRA_LIBS)
+LD=ld
+endif
+
+clientSDK:
+
+all: $(OBJDEST) $(LIBDIR) $(LIBRETROCL)
+
+linktest: $(LIBRETROCL) $(LT_OBJS)
+ $(LINK_EXE_NOLIBSOBJS) -o linktest $(LT_OBJS) $(LIBRETROCL) -Rlib -Rlib/../bin/slapd/lib -Llib -Llib/../bin/slapd/lib -lslapd $(EXTRA_LIBS) $(NSPRLINK)
+
+
+$(LIBRETROCL): $(LIBRETROCL_OBJS) $(RETROCL_DLL_OBJ) $(DEF_FILE)
+ $(LINK_DLL) $(LIBRETROCL_OBJS) $(RETROCL_DLL_OBJ) $(PLATFORMLIBS) $(EXTRA_LIBS) $(LDAP_LIBLDIF) $(NSPRLINK)
+
+tests: $(TEST_PROGS)
+
+veryclean: clean
+
+clean:
+ $(RM) $(LIBRETROCL_OBJS)
+ifeq ($(ARCH), WINNT)
+ $(RM) $(RETROCL_DLL_OBJ)
+endif
+ $(RM) $(LIBRETROCL)
+
+$(OBJDEST):
+ $(MKDIR) $(OBJDEST)
+
+#
+# header file dependencies (incomplete)
+#
+$(LIBRETROCL_OBJS):
diff --git a/ldap/servers/plugins/retrocl/dllmain.c b/ldap/servers/plugins/retrocl/dllmain.c
new file mode 100644
index 00000000..276b1fa9
--- /dev/null
+++ b/ldap/servers/plugins/retrocl/dllmain.c
@@ -0,0 +1,90 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+ /*
+ * Microsoft Windows specifics for LIBRETROCL DLL
+ */
+#include "ldap.h"
+
+
+#ifdef _WIN32
+/* Lifted from Q125688
+ * How to Port a 16-bit DLL to a Win32 DLL
+ * on the MSVC 4.0 CD
+ */
+BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved)
+{
+
+ switch (fdwReason)
+ {
+ case DLL_PROCESS_ATTACH:
+ /* Code from LibMain inserted here. Return TRUE to keep the
+ DLL loaded or return FALSE to fail loading the DLL.
+
+ You may have to modify the code in your original LibMain to
+ account for the fact that it may be called more than once.
+ You will get one DLL_PROCESS_ATTACH for each process that
+ loads the DLL. This is different from LibMain which gets
+ called only once when the DLL is loaded. The only time this
+ is critical is when you are using shared data sections.
+ If you are using shared data sections for statically
+ allocated data, you will need to be careful to initialize it
+ only once. Check your code carefully.
+
+ Certain one-time initializations may now need to be done for
+ each process that attaches. You may also not need code from
+ your original LibMain because the operating system may now
+ be doing it for you.
+ */
+ /*
+ * 16 bit code calls UnlockData()
+ * which is mapped to UnlockSegment in windows.h
+ * in 32 bit world UnlockData is not defined anywhere
+ * UnlockSegment is mapped to GlobalUnfix in winbase.h
+ * and the docs for both UnlockSegment and GlobalUnfix say
+ * ".. function is oboslete. Segments have no meaning
+ * in the 32-bit environment". So we do nothing here.
+ */
+
+ break;
+
+ case DLL_THREAD_ATTACH:
+ /* Called each time a thread is created in a process that has
+ already loaded (attached to) this DLL. Does not get called
+ for each thread that exists in the process before it loaded
+ the DLL.
+
+ Do thread-specific initialization here.
+ */
+ break;
+
+ case DLL_THREAD_DETACH:
+ /* Same as above, but called when a thread in the process
+ exits.
+
+ Do thread-specific cleanup here.
+ */
+ break;
+
+ case DLL_PROCESS_DETACH:
+ /* Code from _WEP inserted here. This code may (like the
+ LibMain) not be necessary. Check to make certain that the
+ operating system is not doing it for you.
+ */
+
+ break;
+ }
+ /* The return value is only used for DLL_PROCESS_ATTACH; all other
+ conditions are ignored. */
+ return TRUE; /* successful DLL_PROCESS_ATTACH */
+}
+#else
+int CALLBACK
+LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine )
+{
+ /*UnlockData( 0 );*/
+ return( 1 );
+}
+#endif
diff --git a/ldap/servers/plugins/retrocl/linktest.c b/ldap/servers/plugins/retrocl/linktest.c
new file mode 100644
index 00000000..22812c57
--- /dev/null
+++ b/ldap/servers/plugins/retrocl/linktest.c
@@ -0,0 +1,16 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+/* This is a test program. Not linked into the shared library */
+
+#include "retrocl.h"
+
+int main(int a,char **b)
+{
+ int r;
+
+ r = retrocl_plugin_init(NULL);
+}
diff --git a/ldap/servers/plugins/retrocl/retrocl.c b/ldap/servers/plugins/retrocl/retrocl.c
new file mode 100644
index 00000000..e0d3e325
--- /dev/null
+++ b/ldap/servers/plugins/retrocl/retrocl.c
@@ -0,0 +1,341 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+/*
+ * Requires that create_instance.c have added a plugin entry similar to:
+
+dn: cn=Retrocl Plugin,cn=plugins,cn=config
+objectclass: top
+objectclass: nsSlapdPlugin
+objectclass: extensibleObject
+cn: RetroCL Plugin
+nsslapd-pluginpath: /export2/servers/Hydra-supplier/lib/retrocl-plugin.so
+nsslapd-plugininitfunc: retrocl_plugin_init
+nsslapd-plugintype: object
+nsslapd-pluginenabled: on
+nsslapd-plugin-depends-on-type: database
+nsslapd-pluginid: retrocl
+nsslapd-pluginversion: 5.0b2
+nsslapd-pluginvendor: Sun Microsystems, Inc.
+nsslapd-plugindescription: Retrocl Plugin
+
+ *
+ */
+
+#include "retrocl.h"
+
+#ifdef _WIN32
+int *module_ldap_debug = 0;
+
+void plugin_init_debug_level(int *level_ptr)
+{
+ module_ldap_debug = level_ptr;
+}
+#endif
+
+void* g_plg_identity [PLUGIN_MAX];
+
+Slapi_Backend *retrocl_be_changelog = NULL;
+
+/* ----------------------------- Retrocl Plugin */
+
+static Slapi_PluginDesc retrocldesc = {"retrocl", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Retrocl Plugin"};
+static Slapi_PluginDesc retroclpostopdesc = {"retrocl-postop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "retrocl post-operation plugin"};
+static Slapi_PluginDesc retroclinternalpostopdesc = {"retrocl-internalpostop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "retrocl internal post-operation plugin"};
+static Slapi_PluginDesc retroclbepostopdesc = {"retrocl-bepostop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Retrocl bepost-operation plugin"};
+
+
+/*
+ * Function: retrocl_*
+ *
+ * Returns: LDAP_
+ *
+ * Arguments: Pb of operation
+ *
+ * Description: wrappers around retrocl_postob registered as callback
+ *
+ */
+
+int retrocl_postop_add (Slapi_PBlock *pb) { return retrocl_postob(pb,OP_ADD);}
+int retrocl_postop_delete (Slapi_PBlock *pb) { return retrocl_postob(pb,OP_DELETE);}
+int retrocl_postop_modify (Slapi_PBlock *pb) { return retrocl_postob(pb,OP_MODIFY);}
+int retrocl_postop_modrdn (Slapi_PBlock *pb) { return retrocl_postob(pb,OP_MODRDN);}
+
+/*
+ * Function: retrocl_postop_init
+ *
+ * Returns: 0/-1
+ *
+ * Arguments: Pb
+ *
+ * Description: callback function
+ *
+ */
+
+int
+retrocl_postop_init( Slapi_PBlock *pb )
+{
+ int rc= 0; /* OK */
+
+ if( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&retroclpostopdesc ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_POST_ADD_FN, (void *) retrocl_postop_add ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_POST_DELETE_FN, (void *) retrocl_postop_delete ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_POST_MODIFY_FN, (void *) retrocl_postop_modify ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_POST_MODRDN_FN, (void *) retrocl_postop_modrdn ) != 0 )
+ {
+ slapi_log_error( SLAPI_LOG_PLUGIN, RETROCL_PLUGIN_NAME, "retrocl_postop_init failed\n" );
+ rc= -1;
+ }
+
+ return rc;
+}
+
+/*
+ * Function: retrocl_internalpostop_init
+ *
+ * Returns: 0/-1
+ *
+ * Arguments: Pb
+ *
+ * Description: callback function
+ *
+ */
+
+int
+retrocl_internalpostop_init( Slapi_PBlock *pb )
+{
+ int rc= 0; /* OK */
+
+ if( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&retroclinternalpostopdesc ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_POST_ADD_FN, (void *) retrocl_postop_add ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_POST_DELETE_FN, (void *) retrocl_postop_delete ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_POST_MODIFY_FN, (void *) retrocl_postop_modify ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_POST_MODRDN_FN, (void *) retrocl_postop_modrdn ) != 0 )
+ {
+ slapi_log_error( SLAPI_LOG_PLUGIN, RETROCL_PLUGIN_NAME, "retrocl_internalpostop_init failed\n" );
+ rc= -1;
+ }
+
+ return rc;
+}
+
+/*
+ * Function: retrocl_rootdse_init
+ *
+ * Returns: LDAP_SUCCESS
+ *
+ * Arguments: none
+ *
+ * Description: The FE DSE *must* be initialised before we get here.
+ *
+ */
+static int retrocl_rootdse_init(void)
+{
+
+ int return_value= LDAP_SUCCESS;
+
+ slapi_config_register_callback(SLAPI_OPERATION_SEARCH,DSE_FLAG_PREOP,"",
+ LDAP_SCOPE_BASE,"(objectclass=*)",
+ retrocl_rootdse_search,NULL);
+ return return_value;
+}
+
+/*
+ * Function: retrocl_select_backend
+ *
+ * Returns: LDAP_
+ *
+ * Arguments: none
+ *
+ * Description: simulates an add of the changelog to see if it exists. If not,
+ * creates it. Then reads the changenumbers. This function should be called
+ * exactly once at startup.
+ *
+ */
+
+static int retrocl_select_backend(void)
+{
+ int err;
+ Slapi_PBlock *pb;
+ Slapi_Backend *be = NULL;
+ Slapi_Entry *referral = NULL;
+ Slapi_Operation *op = NULL;
+ char errbuf[1024];
+
+ pb = slapi_pblock_new();
+
+ slapi_pblock_set (pb, SLAPI_PLUGIN_IDENTITY, g_plg_identity[PLUGIN_RETROCL]);
+
+ /* This is a simulated operation; no actual add is performed */
+ op = operation_new(OP_FLAG_INTERNAL);
+ operation_set_type(op,SLAPI_OPERATION_ADD); /* Ensure be not readonly */
+
+ operation_set_target_spec_str(op,RETROCL_CHANGELOG_DN);
+
+ slapi_pblock_set(pb,SLAPI_OPERATION, op);
+
+ err = slapi_mapping_tree_select(pb,&be,&referral,errbuf);
+ slapi_entry_free(referral);
+
+ operation_free(&op,NULL);
+
+ if (err != LDAP_SUCCESS || be == NULL || be == defbackend_get_backend()) {
+ LDAPDebug(LDAP_DEBUG_TRACE,"Mapping tree select failed (%d) %s.\n",
+ err,errbuf,0);
+
+ /* could not find the backend for cn=changelog, either because
+ * it doesn't exist
+ * mapping tree not registered.
+ */
+ err = retrocl_create_config();
+
+ if (err != LDAP_SUCCESS) return err;
+ } else {
+ retrocl_be_changelog = be;
+ }
+
+ retrocl_create_cle();
+
+ return retrocl_get_changenumbers();
+}
+
+/*
+ * Function: retrocl_get_config_str
+ *
+ * Returns: malloc'ed string which must be freed.
+ *
+ * Arguments: attribute type name
+ *
+ * Description: reads a single-valued string attr from the plugins' own DSE.
+ * This is called twice: to obtain the trim max age during startup, and to
+ * obtain the change log directory. No callback is registered; you cannot
+ * change the trim max age without restarting the server.
+ *
+ */
+
+char *retrocl_get_config_str(const char *attrt)
+{
+ Slapi_Entry **entries;
+ Slapi_PBlock *pb = NULL;
+ char *ma;
+ int rc = 0;
+ char *dn;
+
+ dn = RETROCL_PLUGIN_DN;
+
+ pb = slapi_pblock_new();
+
+ slapi_search_internal_set_pb (pb, dn, LDAP_SCOPE_BASE, "objectclass=*", NULL, 0, NULL,
+ NULL, g_plg_identity[PLUGIN_RETROCL] , 0);
+ slapi_search_internal_pb (pb);
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
+ if (rc != 0) {
+ slapi_pblock_destroy(pb);
+ return NULL;
+ }
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries);
+
+ ma = slapi_entry_attr_get_charptr(entries[0],attrt);
+ slapi_free_search_results_internal(pb);
+ slapi_pblock_destroy(pb);
+
+ return ma;
+}
+
+/*
+ * Function: retrocl_start
+ *
+ * Returns: 0 on success
+ *
+ * Arguments: Pb
+ *
+ * Description:
+ *
+ */
+
+static int retrocl_start (Slapi_PBlock *pb)
+{
+ static int retrocl_started = 0;
+ int rc = 0;
+
+ if (!retrocl_started) {
+ retrocl_rootdse_init();
+
+ rc = retrocl_select_backend();
+
+ if (rc == 0) {
+ retrocl_init_trimming();
+ } else {
+ LDAPDebug(LDAP_DEBUG_TRACE,"Couldnt find backend, not trimming retro changelog (%d).\n",rc,0,0);
+ }
+ }
+
+ retrocl_started = 1;
+ return rc;
+}
+
+/*
+ * Function: retrocl_stop
+ *
+ * Returns: 0
+ *
+ * Arguments: Pb
+ *
+ * Description: called when the server is shutting down
+ *
+ */
+
+static int retrocl_stop (Slapi_PBlock *pb)
+{
+ int rc = 0;
+
+ retrocl_stop_trimming();
+ retrocl_be_changelog = NULL;
+ retrocl_forget_changenumbers();
+
+ return rc;
+}
+
+/*
+ * Function: retrocl_plugin_init
+ *
+ * Returns: 0 on successs
+ *
+ * Arguments: Pb
+ *
+ * Description: main entry point for retrocl
+ *
+ */
+
+int
+retrocl_plugin_init(Slapi_PBlock *pb)
+{
+ static int legacy_initialised= 0;
+ int rc = 0;
+ void *identity = NULL;
+
+ slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &identity);
+ PR_ASSERT (identity);
+ g_plg_identity[PLUGIN_RETROCL] = identity;
+
+ if (!legacy_initialised) {
+ rc= slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 );
+ rc= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&retrocldesc );
+ rc= slapi_pblock_set( pb, SLAPI_PLUGIN_START_FN, (void *) retrocl_start );
+ rc= slapi_pblock_set( pb, SLAPI_PLUGIN_CLOSE_FN, (void *) retrocl_stop );
+
+ rc= slapi_register_plugin("postoperation", 1 /* Enabled */, "retrocl_postop_init", retrocl_postop_init, "Retrocl postoperation plugin", NULL, identity);
+ rc= slapi_register_plugin("internalpostoperation", 1 /* Enabled */, "retrocl_internalpostop_init", retrocl_internalpostop_init, "Retrocl internal postoperation plugin", NULL, identity);
+ }
+
+ legacy_initialised = 1;
+ return rc;
+}
+
+
+
diff --git a/ldap/servers/plugins/retrocl/retrocl.def b/ldap/servers/plugins/retrocl/retrocl.def
new file mode 100644
index 00000000..ce730c44
--- /dev/null
+++ b/ldap/servers/plugins/retrocl/retrocl.def
@@ -0,0 +1,15 @@
+; BEGIN COPYRIGHT BLOCK
+; Copyright 2001 Sun Microsystems, Inc.
+; Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+; All rights reserved.
+; END COPYRIGHT BLOCK
+;
+;
+
+DESCRIPTION 'Netscape Directory Server 7.0 Retro-Changelog Plugin'
+;CODE SHARED READ EXECUTE
+;DATA SHARED READ WRITE
+EXPORTS
+ plugin_init_debug_level @1
+ retrocl_plugin_init @2
+
diff --git a/ldap/servers/plugins/retrocl/retrocl.h b/ldap/servers/plugins/retrocl/retrocl.h
new file mode 100644
index 00000000..f96af64c
--- /dev/null
+++ b/ldap/servers/plugins/retrocl/retrocl.h
@@ -0,0 +1,123 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#ifndef _H_RETROCL
+#define _H_RETROCL 1
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "slapi-private.h"
+#include "slapi-plugin.h"
+/* #include "portable.h" */
+#include "dirver.h"
+#include "ldaplog.h"
+#include "ldif.h"
+#include "slap.h"
+#include <dirlite_strings.h>
+
+/* max len of a long (2^64), represented as a string, including null byte */
+#define CNUMSTR_LEN 21
+typedef unsigned long changeNumber;
+
+typedef struct _cnum_result_t {
+ int crt_nentries; /* number of entries returned from search */
+ int crt_err; /* err returned from backend */
+ Slapi_Entry *crt_entry; /* The entry returned from the backend */
+} cnum_result_t;
+
+typedef struct _cnumRet {
+ changeNumber cr_cnum;
+ char *cr_time;
+ int cr_lderr;
+} cnumRet;
+
+/* Operation types */
+#define OP_MODIFY 1
+#define OP_ADD 2
+#define OP_DELETE 3
+#define OP_MODRDN 4
+
+/*
+ * How often the changelog trimming thread runs. This is the minimum trim age.
+ */
+#define CHANGELOGDB_TRIM_INTERVAL 300*1000 /* 5 minutes */
+
+#define RETROCL_DLL_DEFAULT_THREAD_STACKSIZE 131072L
+#define RETROCL_BE_CACHEMEMSIZE "2097152"
+#define RETROCL_BE_CACHESIZE "-1"
+#define RETROCL_PLUGIN_NAME "DSRetroclPlugin"
+
+/* was originally changelogmaximumage */
+#define CONFIG_CHANGELOG_MAXAGE_ATTRIBUTE "nsslapd-changelogmaxage"
+#define CONFIG_CHANGELOG_DIRECTORY_ATTRIBUTE "nsslapd-changelogdir"
+
+#define RETROCL_CHANGELOG_DN "cn=changelog"
+#define RETROCL_MAPPINGTREE_DN "cn=\"cn=changelog\",cn=mapping tree,cn=config"
+#define RETROCL_PLUGIN_DN "cn=Retro Changelog Plugin,cn=plugins,cn=config"
+#define RETROCL_LDBM_DN "cn=changelog,cn=ldbm database,cn=plugins,cn=config"
+#define RETROCL_INDEX_DN "cn=changenumber,cn=index,cn=changelog,cn=ldbm database,cn=plugins,cn=config"
+
+/* Allow anonymous access to the changelog base only, but not to the
+ * entries in the changelog.
+ */
+#define RETROCL_ACL "(target =\"ldap:///cn=changelog\")(targetattr != \"aci\")(version 3.0; acl \"changelog base\"; allow( read,search, compare ) userdn =\"ldap:///anyone\";)"
+
+enum {
+ PLUGIN_RETROCL,
+ PLUGIN_MAX
+};
+
+extern void* g_plg_identity [PLUGIN_MAX];
+extern Slapi_Backend *retrocl_be_changelog;
+
+extern const char *attr_changenumber;
+extern const char *attr_targetdn;
+extern const char *attr_changetype;
+extern const char *attr_newrdn;
+extern const char *attr_newsuperior;
+extern const char *attr_deleteoldrdn;
+extern const char *attr_changes;
+extern const char *attr_changetime;
+extern const char *attr_objectclass;
+
+extern PRLock *retrocl_internal_lock;
+
+/* Functions */
+
+/* from repl_shared.h: not sure where defined */
+unsigned long strntoul( char *from, size_t len, int base );
+
+extern int retrocl_plugin_init(Slapi_PBlock *pb);
+
+extern int retrocl_bepostop_delete (Slapi_PBlock *pb);
+extern int retrocl_postop_add (Slapi_PBlock *pb);
+extern int retrocl_postop_delete (Slapi_PBlock *pb);
+extern int retrocl_postop_modify (Slapi_PBlock *pb);
+extern int retrocl_postop_modrdn (Slapi_PBlock *pb);
+extern int retrocl_postob(Slapi_PBlock *,int);
+
+extern int retrocl_rootdse_search (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg);
+
+extern void retrocl_create_cle(void);
+extern int retrocl_create_config(void);
+
+extern changeNumber retrocl_get_first_changenumber(void);
+extern void retrocl_set_first_changenumber(changeNumber cn);
+extern changeNumber retrocl_get_last_changenumber(void);
+extern void retrocl_commit_changenumber(void);
+extern void retrocl_release_changenumber(void);
+extern changeNumber retrocl_assign_changenumber(void);
+extern int retrocl_get_changenumbers(void);
+extern void retrocl_forget_changenumbers(void);
+extern time_t retrocl_getchangetime( int type, int *err );
+
+extern void retrocl_init_trimming(void);
+extern void retrocl_stop_trimming(void);
+extern char *retrocl_get_config_str(const char *attrt);
+
+#endif /* _H_RETROCL */
diff --git a/ldap/servers/plugins/retrocl/retrocl.txt b/ldap/servers/plugins/retrocl/retrocl.txt
new file mode 100644
index 00000000..e82368e8
--- /dev/null
+++ b/ldap/servers/plugins/retrocl/retrocl.txt
@@ -0,0 +1,107 @@
+#
+# BEGIN COPYRIGHT BLOCK
+# Copyright 2001 Sun Microsystems, Inc.
+# Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+# All rights reserved.
+# END COPYRIGHT BLOCK
+#
+
+Changelog user documentation
+Last Updated October 6, 2000
+
+1. Introduction
+
+This document describes a how DS 6.0 provides a change log broadly
+compatible with the Internet Draft draft-good-ldap-changelog-01.txt.
+
+When enabled, the change log appears in the DIT below cn=changelog. It
+consists of a single level of entries, each of class changeLogEntry. This
+object class allows the following attributes:
+ - changeNumber. This attribute is always present and contains a single
+ value, an integer which is unique for each change. The value for later
+ changes is larger than those of any change which is already present.
+ - targetDN. This attribute contains the distinguished name of the entry
+ which was added, modified or deleted. In the case of a ModifyDN operation,
+ the targetDN attribute contains the DN of the entry before it was renamed
+ or moved.
+ - changeType. This attribute contains one of the following values: "add",
+ "delete", "modify" or "modrdn".
+ - changes. This attribute contains the changes made to the entry, in LDIF
+ format, for a add or modify change.
+ - newRDN. This attribute contains the new RDN of the entry, for a modifyDN
+ change.
+ - deleteOldRDN. This attribute contains whether the old RDN of the entry
+ was deleted, for a modifyDN change.
+ - newSuperior. This attribute contains the newSuperior field of the entry,
+ for a modifyDN change.
+
+The change log is implemented in an LDBM database.
+
+2. Configuration
+
+To enable the change log, the following steps should be performed. First,
+change the nsslapd-pluginenabled attribute of the DSE cn=Retrocl Plugin,
+cn=plugins,cn=config to "on" instead of "off", Then start or restart the
+server. The server will automatically create the change log database.
+
+3. Trimming
+
+The entries in the change log may be automatically removed if they are older
+than a specified period of time. This is done by setting the
+changelogmaximumage attribute in the change log plugin DSE cn=Retrocl Plugin,
+cn=plugins,cn=config and restarting the server. If this attribute is not
+present, then changed are not trimmed.
+
+The changelogmaximumage attribute is single-valued, and its value consists of
+two parts: a number and a time units code. The time units codes are:
+ - 's' for seconds,
+ - 'm' for minutes,
+ - 'h' for hours,
+ - 'd' for days,
+ - 'w' for weeks.
+
+For example,
+
+changelogmaximumage: 2d
+
+The minimum value is 5 minutes.
+
+4. Access Control
+
+When the changelog backend is created, the default access control is to allow
+anonymous read, search and compare to the changelog base entry, cn=changelog,
+by anyone. No access is granted, except implicitly to the Directory Manager,
+to any of the entries in the change log.
+
+Read access to the entries in the change log should not be granted to anonymous
+users, as the changes attribute could contain modifications to sensitive
+attribute values (such as passwords). Only authenticated services should be
+allowed to access this information.
+
+5. Protocol interaction
+
+All search and compare operations are supported on the change log database.
+Search operations whose filter is of the form
+(&(changenumber>=X)(changeNumber<=Y) are optimized.
+
+Add or modify operations should not be performed on change log entries in the
+change log database. Change log entries can be deleted if desired. The
+change log base entry, cn=changelog, can be modified if desired, to vary the
+access control policy of the change log database.
+
+6. Caveats
+
+The change log does not currently record changes which are internally
+constructed to resolve conflicts during multi-master replication. As a
+result, the change log should not be used in deployments which use multi-master
+replication with more than two masters or suppliers for a database.
+
+==
+
+root dse firstchangenumber and lastchangenumber
+
+changelogdir attribute
+
+test chaining be
+if changelog db deleted - what happens?
+cannot change trim max age without restarting the server
diff --git a/ldap/servers/plugins/retrocl/retrocl_cn.c b/ldap/servers/plugins/retrocl/retrocl_cn.c
new file mode 100644
index 00000000..3623f4b3
--- /dev/null
+++ b/ldap/servers/plugins/retrocl/retrocl_cn.c
@@ -0,0 +1,391 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#include "retrocl.h"
+
+static changeNumber retrocl_internal_cn = 0;
+static changeNumber retrocl_first_cn = 0;
+PRLock *retrocl_internal_lock = NULL;
+
+/*
+ * Function: a2changeNumber
+ *
+ * Returns: changeNumber (long)
+ *
+ * Arguments: string
+ *
+ * Description: parses the string to a changenumber. changenumbers are
+ * positive integers.
+ *
+ */
+
+static changeNumber a2changeNumber (const char *p)
+{
+ changeNumber c;
+
+ c = strntoul((char *)p,strlen(p),10);
+ return c;
+}
+
+/*
+ * Function: handle_cnum_entry
+ * Arguments: op - pointer to Operation struct for this operation
+ * e - pointer to returned entry.
+ * Returns: nothing
+ * Description: Search result handler for retrocl_getchangenum(). Sets the
+ * op->o_handler_data to point to a structure which contains
+ * the changenumber retrieved and an error code.
+ */
+static int
+handle_cnum_entry( Slapi_Entry *e, void *callback_data )
+{
+ cnumRet *cr = (cnumRet *)callback_data;
+ Slapi_Value *sval=NULL;
+ const struct berval *value;
+
+ cr->cr_cnum = 0UL;
+ cr->cr_time = NULL;
+
+ if ( NULL != e ) {
+ Slapi_Attr *chattr = NULL;
+ sval = NULL;
+ value = NULL;
+ if ( slapi_entry_attr_find( e, attr_changenumber, &chattr ) == 0 ) {
+ slapi_attr_first_value( chattr,&sval );
+ if ( NULL != sval ) {
+ value = slapi_value_get_berval ( sval );
+ if( NULL != value && NULL != value->bv_val &&
+ '\0' != value->bv_val[0]) {
+ cr->cr_cnum = a2changeNumber( value->bv_val );
+ }
+ }
+ }
+ chattr = NULL;
+ sval = NULL;
+ value = NULL;
+
+ chattr = NULL;
+ sval = NULL;
+ value = NULL;
+ if ( slapi_entry_attr_find( e, attr_changetime, &chattr ) == 0 ) {
+ slapi_attr_first_value( chattr,&sval );
+ if ( NULL != sval) {
+ value = slapi_value_get_berval ( sval );
+ if (NULL != value && NULL != value->bv_val &&
+ '\0' != value->bv_val[0]) {
+ cr->cr_time = slapi_ch_strdup( value->bv_val );
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+
+/*
+ * Function: handle_cnum_result
+ * Arguments: err - error code returned from search
+ * callback_data - private data for callback
+ * Returns: nothing
+ * Description: result handler for retrocl_getchangenum(). Sets the cr_lderr
+ * field of the cnumRet struct to the error returned
+ * from the backend.
+ */
+static void
+handle_cnum_result( int err, void *callback_data )
+{
+ cnumRet *cr = (cnumRet *)callback_data;
+ cr->cr_lderr = err;
+}
+
+/*
+ * Function: retrocl_get_changenumbers
+ *
+ * Returns: 0/-1
+ *
+ * Arguments: none
+ *
+ * Description: reads the first and last entry in the changelog to obtain
+ * the starting and ending change numbers.
+ *
+ */
+
+int retrocl_get_changenumbers(void)
+{
+ cnumRet cr;
+
+ if (retrocl_internal_lock == NULL) {
+ retrocl_internal_lock = PR_NewLock();
+
+ if (retrocl_internal_lock == NULL) return -1;
+ }
+
+ if (retrocl_be_changelog == NULL) return -1;
+
+ cr.cr_cnum = 0;
+ cr.cr_time = 0;
+
+ slapi_seq_callback(RETROCL_CHANGELOG_DN,SLAPI_SEQ_FIRST,
+ (char *)attr_changenumber, /* cast away const */
+ NULL,NULL,0,&cr,NULL,handle_cnum_result,
+ handle_cnum_entry, NULL);
+
+ retrocl_first_cn = cr.cr_cnum;
+
+ slapi_ch_free(( void **) &cr.cr_time );
+
+ slapi_seq_callback(RETROCL_CHANGELOG_DN,SLAPI_SEQ_LAST,
+ (char *)attr_changenumber, /* cast away const */
+ NULL,NULL,0,&cr,NULL,handle_cnum_result,
+ handle_cnum_entry, NULL);
+
+ retrocl_internal_cn = cr.cr_cnum;
+
+ slapi_log_error(SLAPI_LOG_PLUGIN,"retrocl","Got changenumbers %d and %d\n",
+ retrocl_first_cn,
+ retrocl_internal_cn);
+
+ slapi_ch_free(( void **) &cr.cr_time );
+
+ return 0;
+}
+
+/*
+ * Function: retrocl_getchangetime
+ * Arguments: type - one of SLAPI_SEQ_FIRST, SLAPI_SEQ_LAST
+ * Returns: The time of the requested change record. If the return value is
+ * NO_TIME, the changelog could not be read.
+ * If err is non-NULL, the memory it points to is set the the
+ * error code returned from the backend. If "type" is not valid,
+ * *err is set to -1.
+ * Description: Get the first or last changenumber stored in the changelog,
+ * depending on the value of argument "type".
+ */
+time_t retrocl_getchangetime( int type, int *err )
+{
+ cnumRet cr;
+ time_t ret;
+
+ if ( type != SLAPI_SEQ_FIRST && type != SLAPI_SEQ_LAST ) {
+ if ( err != NULL ) {
+ *err = -1;
+ }
+ return NO_TIME;
+ }
+ memset( &cr, '\0', sizeof( cnumRet ));
+ slapi_seq_callback( RETROCL_CHANGELOG_DN, type,
+ (char *)attr_changenumber, /* cast away const */
+ NULL,
+ NULL, 0, &cr, NULL,
+ handle_cnum_result, handle_cnum_entry, NULL );
+
+ if ( err != NULL ) {
+ *err = cr.cr_lderr;
+ }
+
+ if ( NULL == cr.cr_time ) {
+ ret = NO_TIME;
+ } else {
+ ret = parse_localTime( cr.cr_time );
+ }
+ slapi_ch_free(( void **) &cr.cr_time );
+ return ret;
+}
+
+/*
+ * Function: retrocl_forget_changenumbers
+ *
+ * Returns: none
+ *
+ * Arguments: none
+ *
+ * Description: used only when the server is shutting down
+ *
+ */
+
+void retrocl_forget_changenumbers(void)
+{
+ if (retrocl_internal_lock == NULL) return;
+
+ PR_Lock(retrocl_internal_lock);
+ retrocl_first_cn = 0;
+ retrocl_internal_cn = 0;
+ PR_Unlock(retrocl_internal_lock);
+}
+
+/*
+ * Function: retrocl_get_first_changenumber
+ *
+ * Returns: changeNumber
+ *
+ * Arguments: none
+ *
+ * Description: used in root DSE
+ *
+ */
+
+changeNumber retrocl_get_first_changenumber(void)
+{
+ changeNumber cn;
+ PR_Lock(retrocl_internal_lock);
+ cn = retrocl_first_cn;
+ PR_Unlock(retrocl_internal_lock);
+ return cn;
+}
+
+/*
+ * Function: retrocl_set_first_changenumber
+ *
+ * Returns: none
+ *
+ * Arguments: changenumber
+ *
+ * Description: used in changelog trimming
+ *
+ */
+
+void retrocl_set_first_changenumber(changeNumber cn)
+{
+ PR_Lock(retrocl_internal_lock);
+ retrocl_first_cn = cn;
+ PR_Unlock(retrocl_internal_lock);
+}
+
+
+/*
+ * Function: retrocl_get_last_changenumber
+ *
+ * Returns:
+ *
+ * Arguments:
+ *
+ * Description: used in root DSE
+ *
+ */
+
+changeNumber retrocl_get_last_changenumber(void)
+{
+ changeNumber cn;
+ PR_Lock(retrocl_internal_lock);
+ cn = retrocl_internal_cn;
+ PR_Unlock(retrocl_internal_lock);
+ return cn;
+}
+
+/*
+ * Function: retrocl_commit_changenumber
+ *
+ * Returns: none
+ *
+ * Arguments: none, lock must be held
+ *
+ * Description: NOTE! MUST BE PRECEEDED BY retrocl_assign_changenumber
+ *
+ */
+
+void retrocl_commit_changenumber(void)
+{
+ if ( retrocl_first_cn == 0) {
+ retrocl_first_cn = retrocl_internal_cn;
+ }
+}
+
+/*
+ * Function: retrocl_release_changenumber
+ *
+ * Returns: none
+ *
+ * Arguments: none, lock must be held
+ *
+ * Description: NOTE! MUST BE PRECEEDED BY retrocl_assign_changenumber
+ *
+ */
+
+void retrocl_release_changenumber(void)
+{
+ retrocl_internal_cn--;
+}
+
+/*
+ * Function: retrocl_update_lastchangenumber
+ *
+ * Returns: 0/-1
+ *
+ * Arguments: none
+ *
+ * Description: reads the last entry in the changelog to obtain
+ * the last change number.
+ *
+ */
+
+int retrocl_update_lastchangenumber(void)
+{
+ cnumRet cr;
+
+ if (retrocl_internal_lock == NULL) {
+ retrocl_internal_lock = PR_NewLock();
+
+ if (retrocl_internal_lock == NULL) return -1;
+ }
+
+ if (retrocl_be_changelog == NULL) return -1;
+
+ cr.cr_cnum = 0;
+ cr.cr_time = 0;
+ slapi_seq_callback(RETROCL_CHANGELOG_DN,SLAPI_SEQ_LAST,
+ (char *)attr_changenumber, /* cast away const */
+ NULL,NULL,0,&cr,NULL,handle_cnum_result,
+ handle_cnum_entry, NULL);
+
+
+ retrocl_internal_cn = cr.cr_cnum;
+ slapi_log_error(SLAPI_LOG_PLUGIN,"retrocl","Refetched last changenumber = %d \n",
+ retrocl_internal_cn);
+
+ slapi_ch_free(( void **) &cr.cr_time );
+
+ return 0;
+}
+
+
+
+/*
+ * Function: retrocl_assign_changenumber
+ *
+ * Returns: change number, 0 on error
+ *
+ * Arguments: none. Lock must be held.
+ *
+ * Description: NOTE! MUST BE FOLLOWED BY retrocl_commit_changenumber or
+ * retrocl_release_changenumber
+ *
+ */
+
+changeNumber retrocl_assign_changenumber(void)
+{
+ changeNumber cn;
+
+ if (retrocl_internal_lock == NULL) return 0;
+
+ /* Before we assign the changenumber; we should check for the
+ * validity of the internal assignment of retrocl_internal_cn
+ * we had from the startup */
+
+ if(retrocl_internal_cn <= retrocl_first_cn){
+ /* the numbers have become out of sync - retrocl_get_changenumbers
+ * gets called only once during startup and it may have had a problem
+ * getting the last changenumber.
+ * If there was any problem then update the lastchangenumber from the changelog db.
+ * This function is being called by only the thread that is actually writing
+ * to the changelog.
+ */
+ retrocl_update_lastchangenumber();
+ }
+
+ retrocl_internal_cn++;
+ cn = retrocl_internal_cn;
+ return cn;
+}
diff --git a/ldap/servers/plugins/retrocl/retrocl_create.c b/ldap/servers/plugins/retrocl/retrocl_create.c
new file mode 100644
index 00000000..fbda8e36
--- /dev/null
+++ b/ldap/servers/plugins/retrocl/retrocl_create.c
@@ -0,0 +1,317 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#include "retrocl.h"
+
+/*
+The changelog is created by
+ - changing the node in the dse tree which represents the changelog plugin
+ to enabled on,
+ - shutting down the server,
+ - starting the server.
+ */
+
+/******************************/
+
+/*
+ * Function: retrocl_create_be
+ *
+ * Returns: LDAP_
+ *
+ * Arguments: location in file system to put changelog, or NULL for default
+ *
+ * Description:
+ * add an entry of class nsBackendInstance below cn=ldbm,cn=plugins,cn=config
+ *
+ */
+
+static int retrocl_create_be(const char *bedir)
+{
+ Slapi_PBlock *pb = NULL;
+ Slapi_Entry *e;
+ struct berval *vals[2];
+ struct berval val;
+ int rc;
+
+ vals[0] = &val;
+ vals[1] = NULL;
+
+ e = slapi_entry_alloc();
+ slapi_entry_set_dn(e,slapi_ch_strdup(RETROCL_LDBM_DN));
+
+ /* Set the objectclass attribute */
+ val.bv_val = "top";
+ val.bv_len = 3;
+ slapi_entry_add_values( e, "objectclass", vals );
+
+ /* Set the objectclass attribute */
+ val.bv_val = "extensibleObject";
+ val.bv_len = strlen(val.bv_val);
+ slapi_entry_add_values( e, "objectclass", vals );
+
+ /* Set the objectclass attribute */
+ val.bv_val = "nsBackendInstance";
+ val.bv_len = strlen(val.bv_val);
+ slapi_entry_add_values( e, "objectclass", vals );
+
+ val.bv_val = "changelog";
+ val.bv_len = strlen(val.bv_val);
+ slapi_entry_add_values( e, "cn", vals );
+
+ val.bv_val = RETROCL_BE_CACHESIZE;
+ val.bv_len = strlen(val.bv_val);
+ slapi_entry_add_values( e, "nsslapd-cachesize", vals );
+
+ val.bv_val = RETROCL_CHANGELOG_DN;
+ val.bv_len = strlen(val.bv_val);
+ slapi_entry_add_values( e, "nsslapd-suffix", vals );
+
+ val.bv_val = RETROCL_BE_CACHEMEMSIZE;
+ val.bv_len = strlen(val.bv_val);
+ slapi_entry_add_values( e, "nsslapd-cachememsize", vals );
+
+ val.bv_val = "off";
+ val.bv_len = strlen(val.bv_val);
+ slapi_entry_add_values( e, "nsslapd-readonly", vals );
+
+ if (bedir) {
+ val.bv_val = (char *)bedir; /* cast const */
+ val.bv_len = strlen(val.bv_val);
+ slapi_entry_add_values( e, "nsslapd-directory", vals );
+ }
+
+ pb = slapi_pblock_new ();
+ slapi_add_entry_internal_set_pb( pb, e, NULL /* controls */,
+ g_plg_identity[PLUGIN_RETROCL],
+ 0 /* actions */ );
+ slapi_add_internal_pb (pb);
+ slapi_pblock_get( pb, SLAPI_PLUGIN_INTOP_RESULT, &rc );
+ slapi_pblock_destroy(pb);
+
+ if (rc == 0) {
+ slapi_log_error (SLAPI_LOG_PLUGIN, RETROCL_PLUGIN_NAME,
+ "created changelog database node\n");
+ } else if (rc == LDAP_ALREADY_EXISTS) {
+ slapi_log_error (SLAPI_LOG_PLUGIN, RETROCL_PLUGIN_NAME,
+ "changelog database node already existed\n");
+ } else {
+ slapi_log_error( SLAPI_LOG_FATAL, RETROCL_PLUGIN_NAME, "Changelog LDBM backend could not be created (%d)\n", rc);
+ return rc;
+ }
+
+
+ /* we need the changenumber indexed */
+ e = slapi_entry_alloc();
+ slapi_entry_set_dn(e,slapi_ch_strdup(RETROCL_INDEX_DN));
+
+ /* Set the objectclass attribute */
+ val.bv_val = "top";
+ val.bv_len = 3;
+ slapi_entry_add_values( e, "objectclass", vals );
+
+ /* Set the objectclass attribute */
+ val.bv_val = "nsIndex";
+ val.bv_len = strlen(val.bv_val);
+ slapi_entry_add_values( e, "objectclass", vals );
+
+ val.bv_val = "changenumber";
+ val.bv_len = strlen(val.bv_val);
+ slapi_entry_add_values( e, "cn", vals );
+
+ val.bv_val = "false";
+ val.bv_len = strlen(val.bv_val);
+ slapi_entry_add_values( e, "nssystemindex", vals );
+
+ val.bv_val = "eq";
+ val.bv_len = strlen(val.bv_val);
+ slapi_entry_add_values( e, "nsindextype", vals );
+
+ pb = slapi_pblock_new ();
+ slapi_add_entry_internal_set_pb( pb, e, NULL /* controls */,
+ g_plg_identity[PLUGIN_RETROCL],
+ 0 /* actions */ );
+ slapi_add_internal_pb (pb);
+ slapi_pblock_get( pb, SLAPI_PLUGIN_INTOP_RESULT, &rc );
+ slapi_pblock_destroy(pb);
+
+ if (rc == 0) {
+ slapi_log_error (SLAPI_LOG_PLUGIN, RETROCL_PLUGIN_NAME,
+ "created changenumber index node\n");
+ } else if (rc == LDAP_ALREADY_EXISTS) {
+ slapi_log_error (SLAPI_LOG_PLUGIN, RETROCL_PLUGIN_NAME,
+ "changelog index node already existed\n");
+ } else {
+ slapi_log_error( SLAPI_LOG_FATAL, RETROCL_PLUGIN_NAME, "Changelog LDBM backend changenumber index could not be created (%d)\n", rc);
+ return rc;
+ }
+
+ return rc;
+}
+
+/*
+ * Function: retrocl_create_config
+ *
+ * Returns: LDAP_
+ *
+ * Arguments: none
+ *
+ * Description:
+ * This function is called if there was no mapping tree node or backend for
+ * cn=changelog.
+ */
+int retrocl_create_config(void)
+{
+ Slapi_PBlock *pb = NULL;
+ Slapi_Entry *e;
+ struct berval *vals[2];
+ struct berval val;
+ int rc;
+
+ vals[0] = &val;
+ vals[1] = NULL;
+
+ /* Assume the mapping tree node is missing. It doesn't hurt to
+ * attempt to add it if it already exists. You will see a warning
+ * in the errors file when the referenced backend does not exist.
+ */
+ e = slapi_entry_alloc();
+ slapi_entry_set_dn(e,slapi_ch_strdup(RETROCL_MAPPINGTREE_DN));
+
+ /* Set the objectclass attribute */
+ val.bv_val = "top";
+ val.bv_len = 3;
+ slapi_entry_add_values( e, "objectclass", vals );
+
+
+ /* Set the objectclass attribute */
+ val.bv_val = "extensibleObject";
+ val.bv_len = strlen(val.bv_val);
+ slapi_entry_add_values( e, "objectclass", vals );
+
+ /* Set the objectclass attribute */
+ val.bv_val = "nsMappingTree";
+ val.bv_len = strlen(val.bv_val);
+ slapi_entry_add_values( e, "objectclass", vals );
+
+ val.bv_val = "backend";
+ val.bv_len = strlen(val.bv_val);
+ slapi_entry_add_values( e, "nsslapd-state", vals );
+
+ val.bv_val = RETROCL_CHANGELOG_DN;
+ val.bv_len = strlen(val.bv_val);
+ slapi_entry_add_values( e, "cn", vals );
+
+ val.bv_val = "changelog";
+ val.bv_len = strlen(val.bv_val);
+ slapi_entry_add_values( e, "nsslapd-backend", vals );
+
+ pb = slapi_pblock_new ();
+ slapi_add_entry_internal_set_pb( pb, e, NULL /* controls */,
+ g_plg_identity[PLUGIN_RETROCL],
+ 0 /* actions */ );
+ slapi_add_internal_pb (pb);
+ slapi_pblock_get( pb, SLAPI_PLUGIN_INTOP_RESULT, &rc );
+ slapi_pblock_destroy(pb);
+
+ if (rc == 0) {
+ slapi_log_error (SLAPI_LOG_PLUGIN, RETROCL_PLUGIN_NAME,
+ "created changelog mapping tree node\n");
+ } else if (rc == LDAP_ALREADY_EXISTS) {
+ slapi_log_error (SLAPI_LOG_PLUGIN, RETROCL_PLUGIN_NAME,
+ "changelog mapping tree node already existed\n");
+ } else {
+ slapi_log_error( SLAPI_LOG_FATAL, RETROCL_PLUGIN_NAME, "cn=\"cn=changelog\",cn=mapping tree,cn=config could not be created (%d)\n", rc);
+ return rc;
+ }
+
+ retrocl_be_changelog = slapi_be_select_by_instance_name("changelog");
+
+ if (retrocl_be_changelog == NULL) {
+ /* This is not the nsslapd-changelogdir from cn=changelog4,cn=config */
+ char *bedir;
+
+ bedir = retrocl_get_config_str(CONFIG_CHANGELOG_DIRECTORY_ATTRIBUTE);
+
+ if (bedir == NULL) {
+ /* none specified */
+ }
+
+ rc = retrocl_create_be(bedir);
+ slapi_ch_free ((void **)&bedir);
+ if (rc != LDAP_SUCCESS && rc != LDAP_ALREADY_EXISTS) {
+ return rc;
+ }
+
+ retrocl_be_changelog = slapi_be_select_by_instance_name("changelog");
+ }
+
+ return LDAP_SUCCESS;
+}
+
+/******************************/
+
+/* Function: retrocl_create_cle
+ *
+ * Arguments: none
+ * Returns: nothing
+ * Description: Attempts to create the cn=changelog entry which might already
+ * exist.
+ */
+
+void retrocl_create_cle (void)
+{
+ Slapi_PBlock *pb = NULL;
+ Slapi_Entry *e;
+ int rc;
+ struct berval *vals[2];
+ struct berval val;
+
+ vals[0] = &val;
+ vals[1] = NULL;
+
+ e = slapi_entry_alloc();
+ slapi_entry_set_dn(e,slapi_ch_strdup(RETROCL_CHANGELOG_DN));
+
+ /* Set the objectclass attribute */
+ val.bv_val = "top";
+ val.bv_len = strlen(val.bv_val);
+ slapi_entry_add_values( e, "objectclass", vals );
+
+
+ /* Set the objectclass attribute */
+ val.bv_val = "nsContainer";
+ val.bv_len = strlen(val.bv_val);
+ slapi_entry_add_values( e, "objectclass", vals );
+
+
+ /* Set the objectclass attribute */
+ val.bv_val = "changelog";
+ val.bv_len = strlen(val.bv_val);
+ slapi_entry_add_values( e, "cn", vals );
+
+ val.bv_val = RETROCL_ACL;
+ val.bv_len = strlen(val.bv_val);
+ slapi_entry_add_values( e, "aci", vals );
+
+ pb = slapi_pblock_new ();
+ slapi_add_entry_internal_set_pb( pb, e, NULL /* controls */,
+ g_plg_identity[PLUGIN_RETROCL],
+ 0 /* actions */ );
+ slapi_add_internal_pb (pb);
+ slapi_pblock_get( pb, SLAPI_PLUGIN_INTOP_RESULT, &rc );
+ slapi_pblock_destroy(pb);
+
+ if (rc == 0) {
+ slapi_log_error (SLAPI_LOG_PLUGIN, RETROCL_PLUGIN_NAME,
+ "created cn=changelog\n");
+ } else if (rc == LDAP_ALREADY_EXISTS) {
+ slapi_log_error (SLAPI_LOG_PLUGIN, RETROCL_PLUGIN_NAME,
+ "cn=changelog already existed\n");
+ } else {
+ slapi_log_error( SLAPI_LOG_FATAL, RETROCL_PLUGIN_NAME, "cn=changelog could not be created (%d)\n", rc);
+ }
+}
+
diff --git a/ldap/servers/plugins/retrocl/retrocl_po.c b/ldap/servers/plugins/retrocl/retrocl_po.c
new file mode 100644
index 00000000..96512a51
--- /dev/null
+++ b/ldap/servers/plugins/retrocl/retrocl_po.c
@@ -0,0 +1,529 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#include "retrocl.h"
+
+static int
+entry2reple( Slapi_Entry *e, Slapi_Entry *oe );
+
+static int
+mods2reple( Slapi_Entry *e, LDAPMod **ldm );
+
+static int
+modrdn2reple( Slapi_Entry *e, const char *newrdn, int deloldrdn,
+ LDAPMod **ldm, const char *newsup );
+
+/******************************/
+
+const char *attr_changenumber = "changenumber";
+const char *attr_targetdn = "targetdn";
+const char *attr_changetype = "changetype";
+const char *attr_newrdn = "newrdn";
+const char *attr_deleteoldrdn = "deleteoldrdn";
+const char *attr_changes = "changes";
+const char *attr_newsuperior = "newsuperior";
+const char *attr_changetime = "changetime";
+const char *attr_objectclass = "objectclass";
+
+/*
+ * Function: make_changes_string
+ *
+ * Returns:
+ *
+ * Arguments:
+ *
+ * Description:
+ * loop through the LDAPMod struct and construct the changes attribute/
+ *
+ */
+
+static lenstr *make_changes_string(LDAPMod **ldm, const char **includeattrs)
+{
+ lenstr *l;
+ int i, j, len;
+ int skip;
+
+ l = lenstr_new();
+
+ for ( i = 0; ldm[ i ] != NULL; i++ ) {
+ /* If a list of explicit attributes was given, only add those */
+ if ( NULL != includeattrs ) {
+ skip = 1;
+ for ( j = 0; includeattrs[ j ] != NULL; j++ ) {
+ if ( strcasecmp( includeattrs[ j ], ldm[ i ]->mod_type ) == 0 ) {
+ skip = 0;
+ break;
+ }
+ }
+ if ( skip ) {
+ continue;
+ }
+ }
+ switch ( ldm[ i ]->mod_op & ~LDAP_MOD_BVALUES ) {
+ case LDAP_MOD_ADD:
+ addlenstr( l, "add: " );
+ addlenstr( l, ldm[ i ]->mod_type );
+ addlenstr( l, "\n" );
+ break;
+ case LDAP_MOD_DELETE:
+ addlenstr( l, "delete: " );
+ addlenstr( l, ldm[ i ]->mod_type );
+ addlenstr( l, "\n" );
+ break;
+ case LDAP_MOD_REPLACE:
+ addlenstr( l, "replace: " );
+ addlenstr( l, ldm[ i ]->mod_type );
+ addlenstr( l, "\n" );
+ break;
+ }
+ for ( j = 0; ldm[ i ]->mod_bvalues != NULL &&
+ ldm[ i ]->mod_bvalues[ j ] != NULL; j++ ) {
+ char *buf = NULL;
+ char *bufp = NULL;
+
+ len = strlen( ldm[ i ]->mod_type );
+ len = LDIF_SIZE_NEEDED( len,
+ ldm[ i ]->mod_bvalues[ j ]->bv_len ) + 1;
+ buf = slapi_ch_malloc( len );
+ bufp = buf;
+ ldif_put_type_and_value( &bufp, ldm[ i ]->mod_type,
+ ldm[ i ]->mod_bvalues[ j ]->bv_val,
+ ldm[ i ]->mod_bvalues[ j ]->bv_len );
+ *bufp = '\0';
+
+ addlenstr( l, buf );
+
+ free( buf );
+ }
+ addlenstr( l, "-\n" );
+ }
+ return l;
+}
+
+/*
+ * Function: write_replog_db
+ * Arguments: be - backend to which this change is being applied
+ * optype - type of LDAP operation being logged
+ * dn - distinguished name of entry being changed
+ * log_m - pointer to the actual change operation on a modify
+ * flag - only used by modrdn operations - value of deleteoldrdn
+ * curtime - the current time
+ * Returns: nothing
+ * Description: Given a change, construct an entry which is to be added to the
+ * changelog database.
+ */
+static void
+write_replog_db(
+ int optype,
+ char *dn,
+ LDAPMod **log_m,
+ int flag,
+ time_t curtime,
+ Slapi_Entry *log_e,
+ const char *newrdn,
+ LDAPMod **modrdn_mods,
+ const char *newsuperior
+)
+{
+ char *pat, *edn;
+ struct berval *vals[ 2 ];
+ struct berval val;
+ Slapi_Entry *e;
+ char chnobuf[ 20 ];
+ int err;
+ Slapi_PBlock *pb = NULL;
+ changeNumber changenum;
+
+ PR_Lock(retrocl_internal_lock);
+ changenum = retrocl_assign_changenumber();
+
+ PR_ASSERT( changenum > 0UL );
+ slapi_log_error( SLAPI_LOG_PLUGIN, RETROCL_PLUGIN_NAME,
+ "write_replog_db: write change record %d for dn: \"%s\"\n",
+ changenum, ( dn == NULL ) ? "NULL" : dn );
+
+ /* Construct the dn of this change record */
+ pat = "%s=%lu,%s";
+ edn = slapi_ch_malloc( strlen( pat ) + strlen( RETROCL_CHANGELOG_DN) + 20 );
+ sprintf( edn, pat, attr_changenumber, changenum, RETROCL_CHANGELOG_DN);
+
+ /*
+ * Create the entry struct, and fill in fields common to all types
+ * of change records.
+ */
+ vals[ 0 ] = &val;
+ vals[ 1 ] = NULL;
+
+ e = slapi_entry_alloc();
+ slapi_entry_set_dn( e, slapi_ch_strdup( edn ));
+
+ /* Set the objectclass attribute */
+ val.bv_val = "top";
+ val.bv_len = 3;
+ slapi_entry_add_values( e, "objectclass", vals );
+
+ val.bv_val = "changelogentry";
+ val.bv_len = 14;
+ slapi_entry_add_values( e, "objectclass", vals );
+
+ /* Set the changeNumber attribute */
+ sprintf( chnobuf, "%lu", changenum );
+ val.bv_val = chnobuf;
+ val.bv_len = strlen( chnobuf );
+ slapi_entry_add_values( e, attr_changenumber, vals );
+
+ /* Set the targetentrydn attribute */
+ val.bv_val = dn;
+ val.bv_len = strlen( dn );
+ slapi_entry_add_values( e, attr_targetdn, vals );
+
+ /* Set the changeTime attribute */
+ val.bv_val = format_genTime (curtime);
+ val.bv_len = strlen( val.bv_val );
+ slapi_entry_add_values( e, attr_changetime, vals );
+ free( val.bv_val );
+
+ /*
+ * Finish constructing the entry. How to do it depends on the type
+ * of modification being logged.
+ */
+ err = 0;
+ switch ( optype ) {
+ case OP_ADD:
+ if ( entry2reple( e, log_e ) != 0 ) {
+ err = 1;
+ }
+ break;
+
+ case OP_MODIFY:
+ if ( mods2reple( e, log_m ) != 0 ) {
+ err = 1;
+ }
+ break;
+
+ case OP_MODRDN:
+ if ( modrdn2reple( e, newrdn, flag, modrdn_mods, newsuperior ) != 0 ) {
+ err = 1;
+ }
+ break;
+
+ case OP_DELETE:
+ /* Set the changetype attribute */
+ val.bv_val = "delete";
+ val.bv_len = 6;
+ slapi_entry_add_values( e, attr_changetype, vals );
+ break;
+ default:
+ slapi_log_error( SLAPI_LOG_FATAL, RETROCL_PLUGIN_NAME, "replog: Unknown LDAP operation type "
+ "%d.\n", optype );
+ err = 1;
+ }
+
+ /* Call the repl backend to add this entry */
+ if ( 0 == err ) {
+ int rc;
+
+ pb = slapi_pblock_new ();
+ slapi_add_entry_internal_set_pb( pb, e, NULL /* controls */,
+ g_plg_identity[PLUGIN_RETROCL],
+ 0 /* actions */ );
+ slapi_add_internal_pb (pb);
+ slapi_pblock_get( pb, SLAPI_PLUGIN_INTOP_RESULT, &rc );
+ slapi_pblock_destroy(pb);
+ if ( 0 != rc ) {
+ slapi_log_error( SLAPI_LOG_FATAL, RETROCL_PLUGIN_NAME,
+ "replog: an error occured while adding change "
+ "number %d, dn = %s: %s. \n",
+ changenum, edn, ldap_err2string( rc ));
+ retrocl_release_changenumber();
+ } else {
+ /* Tell the change numbering system this one's committed to disk */
+ retrocl_commit_changenumber( );
+ }
+ } else {
+ slapi_log_error( SLAPI_LOG_FATAL, RETROCL_PLUGIN_NAME,
+ "An error occurred while constructing "
+ "change record number %ld.\n", changenum );
+ retrocl_release_changenumber();
+ }
+ PR_Unlock(retrocl_internal_lock);
+ if ( NULL != edn ) {
+ slapi_ch_free((void **) &edn);
+ }
+
+}
+
+
+/*
+ * Function: entry2reple
+ * Arguments: e - a partially-constructed Slapi_Entry structure
+ * oe - the original entry (an entry being added by a client).
+ * Returns: 0 on success.
+ * Description: Given an Slapi_Entry struct, construct a changelog entry which will be
+ * added to the replication database. It is assumed that e points
+ * to an entry obtained from slapi_entry_alloc().
+ */
+static int
+entry2reple( Slapi_Entry *e, Slapi_Entry *oe )
+{
+ char *p, *estr;
+ struct berval *vals[ 2 ];
+ struct berval val;
+ int len;
+
+ vals[ 0 ] = &val;
+ vals[ 1 ] = NULL;
+
+ /* Set the changetype attribute */
+ val.bv_val = "add";
+ val.bv_len = 3;
+ slapi_entry_add_values( e, attr_changetype, vals );
+
+ estr = slapi_entry2str( oe, &len );
+ p = estr;
+ /* Skip over the dn: line */
+ while (( p = strchr( p, '\n' )) != NULL ) {
+ p++;
+ if ( !ldap_utf8isspace( p )) {
+ break;
+ }
+ }
+ val.bv_val = p;
+ val.bv_len = len - ( p - estr ); /* length + terminating \0 */
+ slapi_entry_add_values( e, attr_changes, vals );
+ free( estr );
+ return 0;
+}
+
+/*
+ * Function: mods2reple
+ * Arguments: e - a partially-constructed Slapi_Entry structure
+ * ldm - an array of pointers to LDAPMod structures describing the
+ * change applied.
+ * Returns: 0 on success.
+ * Description: Given a pointer to an LDAPMod struct and a dn, construct
+ * a new entry which will be added to the replication database.
+ * It is assumed that e points to an entry obtained from
+ * slapi_entry_alloc().
+ */
+static int
+mods2reple( Slapi_Entry *e, LDAPMod **ldm )
+{
+ struct berval val;
+ struct berval *vals[ 2 ];
+ lenstr *l;
+
+ vals[ 0 ] = &val;
+ vals[ 1 ] = NULL;
+
+ /* Set the changetype attribute */
+ val.bv_val = "modify";
+ val.bv_len = 6;
+ slapi_entry_add_values( e, "changetype", vals );
+
+ if (NULL != ldm) {
+ l = make_changes_string( ldm, NULL );
+ if ( NULL != l ) {
+ val.bv_val = l->ls_buf;
+ val.bv_len = l->ls_len + 1; /* string + terminating \0 */
+ slapi_entry_add_values( e, attr_changes, vals );
+ lenstr_free( &l );
+ }
+ }
+ return 0;
+}
+
+
+/*
+ * Function: modrdn2reple
+ * Arguments: e - a partially-constructed Slapi_Entry structure
+ * newrdn - the new relative distinguished name for the entry
+ * deloldrdn - the "deleteoldrdn" flag provided by the client
+ * ldm - any modifications applied as a side-effect of the modrdn
+ * Returns: 0 on success
+ * Description: Given a dn, a new rdn, and a deleteoldrdn flag, construct
+ * a new entry which will be added to the replication database reflecting a
+ * completed modrdn operation. The entry has the same form as above.
+ * It is assumed that e points to an entry obtained from slapi_entry_alloc().
+ */
+static int
+modrdn2reple(
+ Slapi_Entry *e,
+ const char *newrdn,
+ int deloldrdn,
+ LDAPMod **ldm,
+ const char *newsuperior
+)
+{
+ struct berval val;
+ struct berval *vals[ 2 ];
+ lenstr *l;
+ static const char *lastmodattrs[] = {"modifiersname", "modifytimestamp",
+ "creatorsname", "createtimestamp",
+ NULL };
+
+ vals[ 0 ] = &val;
+ vals[ 1 ] = NULL;
+
+ val.bv_val = "modrdn";
+ val.bv_len = 6;
+ slapi_entry_add_values( e, attr_changetype, vals );
+
+ if (newrdn) {
+ val.bv_val = (char *)newrdn; /* cast away const */
+ val.bv_len = strlen( newrdn );
+ slapi_entry_add_values( e, attr_newrdn, vals );
+ }
+
+ if ( deloldrdn == 0 ) {
+ val.bv_val = "FALSE";
+ val.bv_len = 5;
+ } else {
+ val.bv_val = "TRUE";
+ val.bv_len = 4;
+ }
+ slapi_entry_add_values( e, attr_deleteoldrdn, vals );
+
+ if (newsuperior) {
+ val.bv_val = (char *)newsuperior; /* cast away const */
+ val.bv_len = strlen(newsuperior);
+ slapi_entry_add_values(e, attr_newsuperior,vals);
+ }
+
+ if (NULL != ldm) {
+ l = make_changes_string( ldm, lastmodattrs );
+ if ( NULL != l ) {
+ val.bv_val = l->ls_buf;
+ val.bv_len = l->ls_len + 1; /* string + terminating \0 */
+ slapi_entry_add_values( e, attr_changes, vals );
+ lenstr_free( &l );
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Function: retrocl_postob
+ *
+ * Returns: 0 on success
+ *
+ * Arguments: pblock, optype (add, del, modify etc)
+ *
+ * Description: called from retrocl.c op-specific plugins.
+ *
+ * Please be aware that operation threads may be scheduled out between their
+ * being performed inside of the LDBM database and the changelog plugin
+ * running. For example, suppose MA and MB are two modify operations on the
+ * same entry. MA may be performed on the LDBM database, and then block
+ * before the changelog runs. MB then runs against the LDBM database and then
+ * is written to the changelog. MA starts running. In the changelog, MB will
+ * appear to have been performed before MA, but in the LDBM database the
+ * opposite will have occured.
+ *
+ *
+ */
+
+int retrocl_postob (Slapi_PBlock *pb,int optype)
+{
+ char *dn;
+ LDAPMod **log_m = NULL;
+ int flag = 0;
+ Slapi_Entry *te = NULL;
+ Slapi_Operation *op = NULL;
+ LDAPMod **modrdn_mods = NULL;
+ char *newrdn = NULL;
+ char *newsuperior = NULL;
+ Slapi_Backend *be = NULL;
+ time_t curtime;
+ int rc;
+
+ /*
+ * Check to see if the change was made to the replication backend db.
+ * If so, don't try to log it to the db (otherwise, we'd get in a loop).
+ */
+
+ (void)slapi_pblock_get( pb, SLAPI_BACKEND, &be );
+
+ if (slapi_be_logchanges(be) == 0) {
+ LDAPDebug(LDAP_DEBUG_TRACE,"not applying change if not logging\n",
+ 0,0,0);
+ return 0;
+ }
+
+ if (retrocl_be_changelog == NULL || be == retrocl_be_changelog) {
+ LDAPDebug(LDAP_DEBUG_TRACE,"not applying change if no/cl be\n",0,0,0);
+ return 0;
+ }
+
+ slapi_pblock_get(pb, SLAPI_RESULT_CODE, &rc);
+
+ if (rc != LDAP_SUCCESS) {
+ LDAPDebug(LDAP_DEBUG_TRACE,"not applying change if op failed %d\n",rc,
+ 0,0);
+ return 0;
+ }
+
+ if (slapi_op_abandoned(pb)) {
+ LDAPDebug(LDAP_DEBUG_PLUGIN,"not applying change if op abandoned\n",
+ 0,0,0);
+ return 0;
+ }
+
+ curtime = current_time();
+
+ (void)slapi_pblock_get( pb, SLAPI_ORIGINAL_TARGET_DN, &dn );
+
+ /* change number could be retrieved from Operation extension stored in
+ * the pblock, or else it needs to be made dynamically. */
+
+ /* get the operation extension and retrieve the change number */
+ slapi_pblock_get( pb, SLAPI_OPERATION, &op );
+
+ if (op == NULL) {
+ LDAPDebug(LDAP_DEBUG_TRACE,"not applying change if no op\n",0,0,0);
+ return 0;
+ }
+
+ if (operation_is_flag_set(op, OP_FLAG_TOMBSTONE_ENTRY)){
+ LDAPDebug(LDAP_DEBUG_TRACE,"not applying change for nsTombstone entries\n",0,0,0);
+ return 0;
+ }
+
+ switch ( optype ) {
+ case OP_MODIFY:
+ (void)slapi_pblock_get( pb, SLAPI_MODIFY_MODS, &log_m );
+ break;
+ case OP_ADD:
+ /*
+ * For adds, we want the unnormalized dn, so we can preserve
+ * spacing, case, when replicating it.
+ */
+ (void)slapi_pblock_get( pb, SLAPI_ADD_ENTRY, &te );
+ if ( NULL != te ) {
+ dn = slapi_entry_get_dn( te );
+ }
+ break;
+ case OP_DELETE:
+ break;
+ case OP_MODRDN:
+ (void)slapi_pblock_get( pb, SLAPI_MODRDN_NEWRDN, &newrdn );
+ (void)slapi_pblock_get( pb, SLAPI_MODRDN_DELOLDRDN, &flag );
+ (void)slapi_pblock_get( pb, SLAPI_MODIFY_MODS, &modrdn_mods );
+ (void)slapi_pblock_get( pb, SLAPI_MODRDN_NEWSUPERIOR, &newsuperior);
+ break;
+ }
+
+
+ /* check if we should log change to retro changelog, and
+ * if so, do it here */
+ write_replog_db( optype, dn, log_m, flag, curtime, te,
+ newrdn, modrdn_mods, newsuperior );
+
+ return 0;
+}
+
+
diff --git a/ldap/servers/plugins/retrocl/retrocl_rootdse.c b/ldap/servers/plugins/retrocl/retrocl_rootdse.c
new file mode 100644
index 00000000..602858d3
--- /dev/null
+++ b/ldap/servers/plugins/retrocl/retrocl_rootdse.c
@@ -0,0 +1,64 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#include "retrocl.h"
+
+
+
+/*
+ * Function: retrocl_rootdse_search
+ *
+ * Returns: SLAPI_DSE_CALLBACK_OK always
+ *
+ * Arguments: See plugin API
+ *
+ * Description: callback function plugged into base object search of root DSE.
+ * Adds changelog, firstchangenumber and lastchangenumber attributes.
+ *
+ */
+
+int
+retrocl_rootdse_search(Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg)
+{
+
+ struct berval val;
+ struct berval *vals[2];
+ vals[0] = &val;
+ vals[1] = NULL;
+
+ /* Changelog information */
+ if ( retrocl_be_changelog != NULL )
+ {
+ char buf[BUFSIZ];
+ changeNumber cnum;
+
+ /* Changelog suffix */
+ val.bv_val = RETROCL_CHANGELOG_DN;
+ if ( val.bv_val != NULL )
+ {
+ val.bv_len = strlen( val.bv_val );
+ slapi_entry_attr_replace( e, "changelog", vals );
+ }
+
+ /* First change number contained in log */
+ cnum = retrocl_get_first_changenumber();
+ sprintf( buf, "%lu", cnum );
+ val.bv_val = buf;
+ val.bv_len = strlen( val.bv_val );
+ slapi_entry_attr_replace( e, "firstchangenumber", vals );
+
+ /* Last change number contained in log */
+ cnum = retrocl_get_last_changenumber();
+ sprintf( buf, "%lu", cnum );
+ val.bv_val = buf;
+ val.bv_len = strlen( val.bv_val );
+ slapi_entry_attr_replace( e, "lastchangenumber", vals );
+ }
+
+ return SLAPI_DSE_CALLBACK_OK;
+}
+
+
diff --git a/ldap/servers/plugins/retrocl/retrocl_trim.c b/ldap/servers/plugins/retrocl/retrocl_trim.c
new file mode 100644
index 00000000..5c413097
--- /dev/null
+++ b/ldap/servers/plugins/retrocl/retrocl_trim.c
@@ -0,0 +1,505 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+
+#include "retrocl.h"
+
+typedef struct _trim_status {
+ time_t ts_c_max_age; /* Constraint - max age of a changelog entry */
+ time_t ts_s_last_trim; /* Status - last time we trimmed */
+ int ts_s_initialized; /* Status - non-zero if initialized */
+ int ts_s_trimming; /* non-zero if trimming in progress */
+ PRLock *ts_s_trim_mutex; /* protects ts_s_trimming */
+} trim_status;
+static trim_status ts = {0L, 0L, 0, 0, NULL};
+
+/*
+ * All standard changeLogEntry attributes (initialized in get_cleattrs)
+ */
+static const char *cleattrs[ 10 ] = { NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL };
+
+static int retrocl_trimming = 0;
+static Slapi_Eq_Context retrocl_trim_ctx = NULL;
+
+/*
+ * Function: get_cleattrs
+ *
+ * Returns: an array of pointers to attribute names.
+ *
+ * Arguments: None.
+ *
+ * Description: Initializes, if necessary, and returns an array of char *s
+ * with attribute names used for retrieving changeLogEntry
+ * entries from the directory.
+ */
+static const char **get_cleattrs(void)
+{
+ if ( cleattrs[ 0 ] == NULL ) {
+ cleattrs[ 0 ] = attr_objectclass;
+ cleattrs[ 1 ] = attr_changenumber;
+ cleattrs[ 2 ] = attr_targetdn;
+ cleattrs[ 3 ] = attr_changetype;
+ cleattrs[ 4 ] = attr_newrdn;
+ cleattrs[ 5 ] = attr_deleteoldrdn;
+ cleattrs[ 6 ] = attr_changes;
+ cleattrs[ 7 ] = attr_newsuperior;
+ cleattrs[ 8 ] = attr_changetime;
+ cleattrs[ 9 ] = NULL;
+ }
+ return cleattrs;
+}
+
+/*
+ * Function: delete_changerecord
+ *
+ * Returns: LDAP_ error code
+ *
+ * Arguments: the number of the change to delete
+ *
+ * Description:
+ *
+ */
+
+static int
+delete_changerecord( changeNumber cnum )
+{
+ Slapi_PBlock *pb;
+ char *dnbuf;
+ int delrc;
+
+ dnbuf = slapi_ch_malloc( strlen( attr_changenumber ) + 20 +
+ strlen(RETROCL_CHANGELOG_DN));
+ /* Delete the record */
+ sprintf( dnbuf, "%s=%ld, %s", attr_changenumber, cnum,
+ RETROCL_CHANGELOG_DN);
+ pb = slapi_pblock_new ();
+ slapi_delete_internal_set_pb ( pb, dnbuf, NULL /*controls*/, NULL /* uniqueid */,
+ g_plg_identity[PLUGIN_RETROCL], 0 /* actions */ );
+ slapi_delete_internal_pb (pb);
+ slapi_pblock_get( pb, SLAPI_PLUGIN_INTOP_RESULT, &delrc );
+ slapi_pblock_destroy( pb );
+
+ if ( delrc != LDAP_SUCCESS ) {
+ slapi_log_error( SLAPI_LOG_FATAL, RETROCL_PLUGIN_NAME, "delete_changerecord: could not delete "
+ "change record %d\n", cnum );
+ } else {
+ slapi_log_error( SLAPI_LOG_PLUGIN, RETROCL_PLUGIN_NAME,
+ "delete_changerecord: deleted changelog entry \"%s\"\n", dnbuf);
+ }
+ slapi_ch_free((void **) &dnbuf );
+ return delrc;
+}
+
+/*
+ * Function: handle_getchangerecord_result
+ * Arguments: op - pointer to Operation struct for this operation
+ * err - error code returned from search
+ * Returns: nothing
+ * Description: result handler for get_changerecord(). Sets the crt_err
+ * field of the cnum_result_t struct to the error returned
+ * from the backend.
+ */
+static void
+handle_getchangerecord_result( int err, void *callback_data )
+{
+ cnum_result_t *crt = callback_data;
+
+ if ( crt == NULL ) {
+ slapi_log_error( SLAPI_LOG_FATAL, RETROCL_PLUGIN_NAME,
+ "handle_getchangerecord_result: callback_data NULL\n" );
+ } else {
+ crt->crt_err = err;
+ }
+}
+
+/*
+ * Function: handle_getchangerecord_search
+ * Arguments: op - pointer to Operation struct for this operation
+ * e - entry returned by backend
+ * Returns: 0 in all cases
+ * Description: Search result operation handler for get_changerecord().
+ * Sets fields in the cnum_result_t struct pointed to by
+ * op->o_handler_data.
+ */
+static int
+handle_getchangerecord_search( Slapi_Entry *e, void *callback_data)
+{
+ cnum_result_t *crt = callback_data;
+
+ if ( crt == NULL ) {
+ slapi_log_error( SLAPI_LOG_FATAL, RETROCL_PLUGIN_NAME,
+ "handle_getchangerecord_search: op->o_handler_data NULL\n" );
+ } else if ( crt->crt_nentries > 0 ) {
+ /* only return the first entry, I guess */
+ slapi_log_error( SLAPI_LOG_FATAL, RETROCL_PLUGIN_NAME,
+ "handle_getchangerecord_search: multiple entries returned\n" );
+ } else {
+ crt->crt_nentries++;
+ crt->crt_entry = e;
+ }
+
+ return 0;
+}
+
+
+/*
+ * Function: get_changerecord
+ * Arguments: cnum - number of change record to retrieve
+ * Returns: Pointer to an entry structure. The caller must free the entry.
+ * If "err" is non-NULL, an error code is returned in the memory
+ * location it points to.
+ * Description: Retrieve the change record entry whose number is "cnum".
+ */
+static Slapi_Entry *get_changerecord( changeNumber cnum, int *err )
+{
+ cnum_result_t crt, *crtp = &crt;
+ char fstr[ 16 + CNUMSTR_LEN + 2 ];
+ Slapi_PBlock *pb;
+
+ if ( cnum == 0UL ) {
+ if ( err != NULL ) {
+ *err = LDAP_PARAM_ERROR;
+ }
+ return NULL;
+ }
+ crtp->crt_nentries = crtp->crt_err = 0; crtp->crt_entry = NULL;
+ sprintf( fstr, "%s=%ld", attr_changenumber, cnum );
+
+ pb = slapi_pblock_new ();
+ slapi_search_internal_set_pb (pb, RETROCL_CHANGELOG_DN,
+ LDAP_SCOPE_SUBTREE, fstr,
+ (char **)get_cleattrs(), /* cast const */
+ 0 /* attrsonly */,
+ NULL /* controls */, NULL /* uniqueid */,
+ g_plg_identity[PLUGIN_RETROCL],
+ 0 /* actions */);
+
+ slapi_search_internal_callback_pb (pb, crtp,
+ handle_getchangerecord_result,
+ handle_getchangerecord_search, NULL );
+ if ( err != NULL ) {
+ *err = crtp->crt_err;
+ }
+
+ slapi_pblock_destroy (pb);
+
+ return( crtp->crt_entry );
+}
+
+/*
+ * Function: trim_changelog
+ *
+ * Arguments: none
+ *
+ * Returns: 0 on success, -1 on failure
+ *
+ * Description: Trims the changelog, according to the constraints
+ * described by the ts structure.
+ */
+static int trim_changelog(void)
+{
+ int rc = 0, ldrc, done;
+ time_t now;
+ changeNumber first_in_log = 0, last_in_log = 0;
+ Slapi_Entry *e = NULL;
+ int num_deleted = 0;
+ int me,lt;
+
+
+ now = current_time();
+
+ PR_Lock( ts.ts_s_trim_mutex );
+ me = ts.ts_c_max_age;
+ lt = ts.ts_s_last_trim;
+ PR_Unlock( ts.ts_s_trim_mutex );
+
+ if ( now - lt >= (CHANGELOGDB_TRIM_INTERVAL / 1000) ) {
+
+ /*
+ * Trim the changelog. Read sequentially through all the
+ * entries, deleting any which do not meet the criteria
+ * described in the ts structure.
+ */
+ done = 0;
+
+ while ( !done && retrocl_trimming == 1 ) {
+ int did_delete;
+ Slapi_Attr *attr;
+
+ did_delete = 0;
+ first_in_log = retrocl_get_first_changenumber();
+ if ( 0UL == first_in_log ) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, RETROCL_PLUGIN_NAME,
+ "trim_changelog: no changelog records "
+ "to trim\n" );
+ /* Bail out - we can't do any useful work */
+ break;
+ }
+
+ last_in_log = retrocl_get_last_changenumber();
+ if ( last_in_log == first_in_log ) {
+ /* Always leave at least one entry in the change log */
+ break;
+ }
+ if ( me > 0L ) {
+ e = get_changerecord( first_in_log, &ldrc );
+ if ( NULL != e ) {
+ Slapi_Value *sval = NULL;
+ const struct berval *val = NULL;
+ rc = slapi_entry_attr_find( e, attr_changetime, &attr );
+ /* Bug 624442: Logic checking for lack of timestamp was
+ reversed. */
+ if ( 0 != rc || slapi_attr_first_value( attr,&sval ) == -1 ||
+ (val = slapi_value_get_berval ( sval )) == NULL ||
+ NULL == val->bv_val ) {
+ /* What to do if there's no timestamp? Just delete it. */
+ retrocl_set_first_changenumber( first_in_log + 1 );
+ ldrc = delete_changerecord( first_in_log );
+ num_deleted++;
+ did_delete = 1;
+ } else {
+ time_t change_time = parse_localTime( val->bv_val );
+ if ( change_time + me < now ) {
+ retrocl_set_first_changenumber( first_in_log + 1 );
+ ldrc = delete_changerecord( first_in_log );
+ num_deleted++;
+ did_delete = 1;
+ }
+ /* slapi_entry_free( e ); */ /* XXXggood should we be freeing this? */
+ }
+ }
+ }
+ if ( !did_delete ) {
+ done = 1;
+ }
+ }
+ } else {
+ LDAPDebug(LDAP_DEBUG_PLUGIN, "not yet time to trim: %d < (%d+%d)\n",
+ now,lt,(CHANGELOGDB_TRIM_INTERVAL/1000));
+ }
+ PR_Lock( ts.ts_s_trim_mutex );
+ ts.ts_s_trimming = 0;
+ ts.ts_s_last_trim = now;
+ PR_Unlock( ts.ts_s_trim_mutex );
+ if ( num_deleted > 0 ) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, RETROCL_PLUGIN_NAME,
+ "trim_changelog: removed %d change records\n",
+ num_deleted );
+ }
+ return rc;
+}
+
+static int retrocl_active_threads;
+
+/*
+ * Function: changelog_trim_thread_fn
+ *
+ * Returns: nothing
+ *
+ * Arguments: none
+ *
+ * Description: the thread creation callback. retrocl_active_threads is
+ * provided for debugging purposes.
+ *
+ */
+
+static void
+changelog_trim_thread_fn( void *arg )
+{
+ PR_AtomicIncrement(&retrocl_active_threads);
+ trim_changelog();
+ PR_AtomicDecrement(&retrocl_active_threads);
+}
+
+
+
+/*
+ * Function: retrocl_housekeeping
+ * Arguments: cur_time - the current time
+ * Returns: nothing
+ * Description: Determines if it is time to trim the changelog database,
+ * and if so, determines if the changelog database needs to
+ * be trimmed. If so, a thread is started which will trim
+ * the database.
+ */
+
+void retrocl_housekeeping ( time_t cur_time, void *noarg )
+{
+ static time_t thread_start_time;
+ int ldrc;
+
+ if (retrocl_be_changelog == NULL) {
+ LDAPDebug(LDAP_DEBUG_TRACE,"not housekeeping if no cl be\n",0,0,0);
+ return;
+ }
+
+ if ( !ts.ts_s_initialized ) {
+ slapi_log_error( SLAPI_LOG_FATAL, RETROCL_PLUGIN_NAME, "changelog_housekeeping called before "
+ "trimming constraints set\n" );
+ return;
+ }
+
+ PR_Lock( ts.ts_s_trim_mutex );
+ if ( !ts.ts_s_trimming ) {
+ int must_trim = 0;
+ /* See if we need to trim */
+ /* Has enough time elapsed since our last check? */
+ if ( cur_time - ts.ts_s_last_trim >= (ts.ts_c_max_age) ) {
+ /* Is the first entry too old? */
+ time_t first_time;
+ /*
+ * good we could avoid going to the database to retrieve
+ * this time information if we cached the last value we'd read.
+ * But a client might have deleted it over protocol.
+ */
+ first_time = retrocl_getchangetime( SLAPI_SEQ_FIRST, &ldrc );
+ LDAPDebug(LDAP_DEBUG_PLUGIN,
+ "cltrim: ldrc=%d, first_time=%d, cur_time=%d\n",
+ ldrc,first_time,cur_time);
+ if ( LDAP_SUCCESS == ldrc && first_time > (time_t) 0L &&
+ first_time + ts.ts_c_max_age < cur_time ) {
+ must_trim = 1;
+ }
+ }
+ if ( must_trim ) {
+ LDAPDebug(LDAP_DEBUG_TRACE,"changelog about to create thread\n",0,0,0);
+ /* Start a thread to trim the changelog */
+ thread_start_time = cur_time;
+ ts.ts_s_trimming = 1;
+ if ( PR_CreateThread( PR_USER_THREAD,
+ changelog_trim_thread_fn, NULL,
+ PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_UNJOINABLE_THREAD,
+ RETROCL_DLL_DEFAULT_THREAD_STACKSIZE ) == NULL ) {
+ slapi_log_error( SLAPI_LOG_FATAL, RETROCL_PLUGIN_NAME, "unable to create changelog trimming thread\n" );
+ }
+ } else {
+ LDAPDebug(LDAP_DEBUG_PLUGIN,
+ "changelog does not need to be trimmed\n",0,0,0);
+ }
+ }
+ PR_Unlock( ts.ts_s_trim_mutex );
+}
+
+
+/*
+ * Function: age_str2time
+ *
+ * Returns: time_t
+ *
+ * Arguments: string representation of age (digits and unit s,m,h,d or w)
+ *
+ * Description:
+ * convert time from string like 1h (1 hour) to corresponding time in seconds
+ *
+ */
+
+static time_t
+age_str2time (const char *age)
+{
+ char *maxage;
+ char unit;
+ time_t ageval;
+
+ if (age == NULL || age[0] == '\0' || strcmp (age, "0") == 0) {
+ return 0;
+ }
+
+ maxage = slapi_ch_strdup ( age );
+ unit = maxage[ strlen( maxage ) - 1 ];
+ maxage[ strlen( maxage ) - 1 ] = '\0';
+ ageval = strntoul( maxage, strlen( maxage ), 10 );
+ if ( maxage) {
+ slapi_ch_free ( (void **) &maxage );
+ }
+ switch ( unit ) {
+ case 's':
+ break;
+ case 'm':
+ ageval *= 60;
+ break;
+ case 'h':
+ ageval *= ( 60 * 60 );
+ break;
+ case 'd':
+ ageval *= ( 24 * 60 * 60 );
+ break;
+ case 'w':
+ ageval *= ( 7 * 24 * 60 * 60 );
+ break;
+ default:
+ slapi_log_error( SLAPI_LOG_PLUGIN, "retrocl",
+ "age_str2time: unknown unit \"%c\" "
+ "for maxiumum changelog age\n", unit );
+ ageval = -1;
+ }
+
+ return ageval;
+}
+
+/*
+ * Function: retrocl_init_trimming
+ *
+ * Returns: none, exits on fatal error
+ *
+ * Arguments: none
+ *
+ * Description: called during startup
+ *
+ */
+
+void retrocl_init_trimming (void)
+{
+ const char *cl_maxage;
+ time_t ageval;
+
+ cl_maxage = retrocl_get_config_str(CONFIG_CHANGELOG_MAXAGE_ATTRIBUTE);
+
+ if (cl_maxage == NULL) {
+ LDAPDebug(LDAP_DEBUG_TRACE,"No maxage, not trimming retro changelog.\n",0,0,0);
+ return;
+ }
+ ageval = age_str2time (cl_maxage);
+ slapi_ch_free ((void **)&cl_maxage);
+
+ ts.ts_c_max_age = ageval;
+ ts.ts_s_last_trim = (time_t) 0L;
+ ts.ts_s_trimming = 0;
+ if (( ts.ts_s_trim_mutex = PR_NewLock()) == NULL ) {
+ slapi_log_error( SLAPI_LOG_FATAL, RETROCL_PLUGIN_NAME, "set_changelog_trim_constraints: "
+ "cannot create new lock.\n" );
+ exit( 1 );
+ }
+ ts.ts_s_initialized = 1;
+ retrocl_trimming = 1;
+
+ retrocl_trim_ctx = slapi_eq_repeat(retrocl_housekeeping,
+ NULL,(time_t)0,
+ CHANGELOGDB_TRIM_INTERVAL);
+
+}
+
+/*
+ * Function: retrocl_stop_trimming
+ *
+ * Returns: none
+ *
+ * Arguments: none
+ *
+ * Description: called when server is shutting down to ensure trimming stops
+ * eventually.
+ *
+ */
+
+void retrocl_stop_trimming(void)
+{
+ retrocl_trimming = 0;
+ if (retrocl_trim_ctx) {
+ slapi_eq_cancel(retrocl_trim_ctx);
+ retrocl_trim_ctx = NULL;
+ }
+}
+
diff --git a/ldap/servers/plugins/rever/Makefile b/ldap/servers/plugins/rever/Makefile
new file mode 100644
index 00000000..76203a9c
--- /dev/null
+++ b/ldap/servers/plugins/rever/Makefile
@@ -0,0 +1,111 @@
+#
+# BEGIN COPYRIGHT BLOCK
+# Copyright 2001 Sun Microsystems, Inc.
+# Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+# All rights reserved.
+# END COPYRIGHT BLOCK
+#
+#
+# GNU Makefile for Directory Server password_storaged-plugin.so password storage scheme plugins
+#
+
+LDAP_SRC = ../../..
+MCOM_ROOT = ../../../../..
+
+NOSTDCLEAN=true # don't let nsconfig.mk define target clean
+NOSTDSTRIP=true # don't let nsconfig.mk define target strip
+NSPR20=true # probably should be defined somewhere else (not sure where)
+
+OBJDEST = $(OBJDIR)/lib/libdes
+LIBDIR = $(LIB_RELDIR)
+
+include $(MCOM_ROOT)/ldapserver/nsconfig.mk
+include $(MCOM_ROOT)/ldapserver/ns_usepurify.mk
+include $(LDAP_SRC)/nsldap.mk
+
+ifeq ($(ARCH), WINNT)
+DEF_FILE:=./libdes.def
+endif
+
+CFLAGS+=$(SLCFLAGS)
+
+INCLUDES += -I$(LDAP_SRC)/servers/slapd
+INCLUDES += -I$(MCOM_ROOT)/ldapserver/ldap/include
+
+REVER_OBJS= \
+ rever.o des.o
+
+OBJS = $(addprefix $(OBJDEST)/, $(REVER_OBJS))
+
+ifeq ($(ARCH), WINNT)
+LIBREVER_DLL_OBJ = $(addprefix $(OBJDEST)/, dllmain.o)
+endif
+
+REVER_DLL = des-plugin
+LIBREVER = $(addprefix $(LIBDIR)/, $(REVER_DLL).$(DLL_SUFFIX))
+
+ifeq ($(ARCH), WINNT)
+EXTRA_LIBS_DEP += \
+ $(LIBSLAPD_DEP) \
+ $(LDAP_LIBUTIL_DEP) \
+ $(LDAP_COMMON_LIBS_DEP)
+EXTRA_LIBS_DEP += \
+ $(LDAPSDK_DEP) \
+ $(SECURITY_DEP)
+EXTRA_LIBS += \
+ $(LIBSLAPD) \
+ $(LDAP_SDK_LIBLDAP_DLL) \
+ $(LIBUTIL) \
+ $(NSPRLINK) \
+ $(LDAP_COMMON_LIBS) \
+ $(SECURITYLINK)
+endif
+ifeq ($(ARCH), AIX)
+EXTRA_LIBS_DEP += \
+ $(LIBSLAPD_DEP) \
+ $(LDAP_LIBUTIL_DEP) \
+ $(LDAP_COMMON_LIBS_DEP)
+EXTRA_LIBS_DEP += \
+ $(LDAPSDK_DEP) \
+ $(SECURITY_DEP)
+EXTRA_LIBS += \
+ $(LIBSLAPDLINK) \
+ $(LDAP_SDK_LIBLDAP_DLL) \
+ $(LIBUTIL) \
+ $(NSPRLINK) \
+ $(LDAP_COMMON_LIBS) \
+ $(SECURITYLINK)
+endif
+
+ifeq ($(ARCH), HPUX)
+EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAPSDK_DEP) $(NSPR_DEP) $(SECURITY_DEP)
+EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(SECURITYLINK) $(NSPRLINK) $(ICULINK)
+endif
+
+ifeq ($(ARCH), WINNT)
+DLL_LDFLAGS += -def:"./libdes.def"
+CFLAGS+= /WX
+endif # WINNT
+
+ifeq ($(ARCH), AIX)
+LD=ld
+endif
+
+clientSDK:
+
+all: $(OBJDEST) $(LIBDIR) $(LIBREVER)
+
+$(LIBREVER): $(OBJS) $(LIBREVER_DLL_OBJ) $(DEF_FILE)
+ $(LINK_DLL) $(LIBREVER_DLL_OBJ) $(EXTRA_LIBS)
+
+veryclean: clean
+
+clean:
+ $(RM) $(OBJS)
+ifeq ($(ARCH), WINNT)
+ $(RM) $(LIBREVER_DLL_OBJ)
+endif
+ $(RM) $(LIBREVER)
+
+$(OBJDEST):
+ $(MKDIR) $(OBJDEST)
diff --git a/ldap/servers/plugins/rever/des.c b/ldap/servers/plugins/rever/des.c
new file mode 100644
index 00000000..4b70c91d
--- /dev/null
+++ b/ldap/servers/plugins/rever/des.c
@@ -0,0 +1,465 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* from /usr/project/iplanet/ws/ds5.ke/ns/svrcore/pkcs7/tstarchive.c */
+
+#include <string.h>
+#include <stdio.h>
+
+#include <ldap.h>
+#include <nspr.h>
+#include <nss.h>
+#include <secmod.h>
+/*
+#include <secasn1.h>
+#include <secpkcs7.h>
+*/
+#include <key.h>
+#include <certdb.h>
+#include <cert.h>
+#include <svrcore.h>
+#include <secmodt.h>
+#include <prtypes.h>
+#include <seccomon.h>
+#include <pk11func.h>
+
+#include "rever.h"
+#include <slap.h>
+#include "slapi-plugin.h"
+#include <uuid.h>
+
+
+struct pk11MechItem
+{
+ CK_MECHANISM_TYPE type;
+ const char *mechName;
+};
+static const struct pk11MechItem mymech = { CKM_DES_CBC, "DES CBC encryption" };
+
+
+static Slapi_Mutex *mylock = NULL;
+
+struct pk11ContextStore
+{
+ PK11SlotInfo *slot;
+ const struct pk11MechItem *mech;
+
+ PK11SymKey *key;
+ SECItem *params;
+
+ int length;
+ unsigned char *crypt;
+};
+
+static int encode_path(char *inPlain, char **outCipher, char *path);
+static int decode_path(char *inCipher, char **outPlain, char *path);
+static SVRCOREError genKey(struct pk11ContextStore **out, const char *token, char *path);
+static SVRCOREError cryptPassword(struct pk11ContextStore *store, char * clear, unsigned char **out);
+static SVRCOREError decryptPassword(struct pk11ContextStore *store, unsigned char *cipher, char **out, int len);
+static void freeDes(struct pk11ContextStore *out);
+
+static int init = 0;
+
+void
+init_des_plugin()
+{
+ mylock = slapi_new_mutex();
+}
+
+int
+encode(char *inPlain, char **outCipher)
+{
+ return encode_path(inPlain, outCipher, NULL);
+}
+
+static int
+encode_path(char *inPlain, char **outCipher, char *path)
+{
+ struct pk11ContextStore *context = NULL;
+ int err;
+
+ unsigned char *cipher = NULL;
+ char *tmp = NULL;
+ char *base = NULL;
+
+
+ *outCipher = NULL;
+ err = 1;
+
+ if ( genKey(&context, tokDes, path) == SVRCORE_Success )
+ {
+ /* Try an encryption */
+ if ( cryptPassword(context, inPlain, &cipher) == SVRCORE_Success )
+ {
+ base = BTOA_DataToAscii(cipher, context->length);
+ if ( base != NULL )
+ {
+ tmp = slapi_ch_malloc( 3 + strlen(REVER_SCHEME_NAME) + strlen(base));
+ if ( tmp != NULL )
+ {
+ sprintf( tmp, "%c%s%c%s", PWD_HASH_PREFIX_START, REVER_SCHEME_NAME, PWD_HASH_PREFIX_END, base);
+ *outCipher = tmp;
+ tmp = NULL;
+ err = 0;
+ }
+ PORT_Free(base);
+ }
+ }
+ }
+
+ freeDes(context);
+ slapi_ch_free((void **) &context);
+ return(err);
+}
+
+int
+decode(char *inCipher, char **outPlain)
+{
+ return decode_path(inCipher, outPlain, NULL);
+}
+
+
+static int
+decode_path(char *inCipher, char **outPlain, char *path)
+{
+ struct pk11ContextStore *context = NULL;
+ char *plain= NULL;
+ int err;
+
+ unsigned char *base = NULL;
+ int len = 0;
+
+
+ *outPlain = NULL;
+ err = 1;
+
+ if ( genKey(&context, tokDes, path) == SVRCORE_Success )
+ {
+ /* it seems that there is memory leak in that function: bug 400170 */
+
+ base = ATOB_AsciiToData(inCipher, (unsigned int*)&len);
+ if ( base != NULL )
+ {
+ if ( decryptPassword(context, base, &plain, len) == SVRCORE_Success )
+ {
+ *outPlain = plain;
+ err = 0;
+ }
+ }
+ }
+
+ PORT_Free(base);
+ freeDes(context);
+ slapi_ch_free((void **) &context);
+ return(err);
+}
+
+static void freeDes(struct pk11ContextStore *out)
+{
+ if (out)
+ {
+ if (out->slot)
+ slapd_pk11_freeSlot(out->slot);
+ if (out->key)
+ slapd_pk11_freeSymKey(out->key);
+ if (out->params)
+ SECITEM_FreeItem(out->params,PR_TRUE);
+ if (out->crypt)
+ free(out->crypt);
+ }
+}
+
+static SVRCOREError genKey(struct pk11ContextStore **out, const char *token, char *path)
+{
+ SVRCOREError err = SVRCORE_Success;
+ struct pk11ContextStore *store = NULL;
+ SECItem *pwitem = NULL;
+ SECItem *result = NULL;
+ SECAlgorithmID *algid = NULL;
+ SECOidTag algoid;
+ SECItem *salt = NULL;
+ CK_MECHANISM pbeMech;
+ CK_MECHANISM cryptoMech;
+
+ char *instancedir = NULL;
+ char *iv = NULL;
+
+ store = (struct pk11ContextStore*)slapi_ch_malloc(sizeof(*store));
+ if (store == NULL)
+ {
+ return (err = SVRCORE_NoMemory_Error);
+ }
+ *out = store;
+
+ /* Low-level init */
+ store->slot = NULL;
+ store->key = NULL;
+ store->params = NULL;
+ store->crypt = NULL;
+
+ /* Use the tokenName to find a PKCS11 slot */
+ store->slot = slapd_pk11_findSlotByName((char *)token);
+ if (store->slot == NULL)
+ {
+ return (err = SVRCORE_NoSuchToken_Error);
+ }
+
+ /* Generate a key and parameters to do the encryption */
+ store->mech = &mymech;
+
+ /* Generate a unique id, used as salt for the key generation */
+ if ( path == NULL )
+ {
+ instancedir = config_get_instancedir();
+ if ( instancedir == NULL )
+ {
+ return (err = SVRCORE_System_Error);
+ }
+ }
+ else
+ {
+ instancedir = slapi_ch_strdup(path);
+ }
+ if ( slapi_uniqueIDGenerateFromNameString (&iv, NULL, instancedir, strlen(instancedir)) != UID_SUCCESS )
+ {
+ slapi_ch_free((void**)&instancedir);
+ return (err = SVRCORE_System_Error);
+ }
+ slapi_ch_free((void**)&instancedir);
+
+ pwitem = (SECItem *) PORT_Alloc(sizeof(SECItem));
+ if (pwitem == NULL)
+ {
+ return (err = SVRCORE_NoMemory_Error);
+ }
+ pwitem->type = siBuffer;
+ pwitem->data = (unsigned char *)PORT_Alloc(strlen(iv)+1);
+ if (pwitem->data == NULL)
+ {
+ return (err = SVRCORE_NoMemory_Error);
+ }
+ strcpy((char*)pwitem->data, iv);
+ pwitem->len = strlen(iv) + 1;
+
+ algoid = SEC_OID_PKCS5_PBE_WITH_MD2_AND_DES_CBC;
+
+ salt = (SECItem *) PORT_Alloc(sizeof(SECItem));
+ if (salt == NULL)
+ {
+ return (err = SVRCORE_NoMemory_Error);
+ }
+ salt->type = siBuffer;
+ salt->data = (unsigned char *)PORT_Alloc(strlen(iv)+1);
+ if ( salt->data == NULL )
+ {
+ return (err = SVRCORE_NoMemory_Error);
+ }
+ strcpy((char*)salt->data, iv);
+ salt->len = strlen(iv) + 1;
+ slapi_ch_free((void**)&iv);
+
+ algid = slapd_pk11_createPBEAlgorithmID(algoid, 2, salt);
+
+ slapi_lock_mutex(mylock);
+ store->key = slapd_pk11_pbeKeyGen(store->slot, algid, pwitem, 0, 0);
+ if (store->key == 0)
+ {
+ slapi_unlock_mutex(mylock);
+ return (err = SVRCORE_System_Error);
+ }
+
+ slapi_unlock_mutex(mylock);
+ pbeMech.mechanism = slapd_pk11_algtagToMechanism(algoid);
+ result = slapd_pk11_paramFromAlgid(algid);
+ secoid_destroyAlgorithmID(algid, PR_TRUE);
+ pbeMech.pParameter = result->data;
+ pbeMech.ulParameterLen = result->len;
+ if(slapd_pk11_mapPBEMechanismToCryptoMechanism(&pbeMech, &cryptoMech, pwitem,
+ PR_FALSE) != CKR_OK)
+ {
+ SECITEM_FreeItem(result, PR_TRUE);
+ return (err = SVRCORE_System_Error);
+ }
+ SECITEM_FreeItem(result, PR_TRUE);
+ SECITEM_FreeItem(pwitem, PR_TRUE);
+ SECITEM_FreeItem(salt, PR_TRUE);
+ store->params = (SECItem *) PORT_Alloc(sizeof(SECItem));
+ if (store->params == NULL)
+ {
+ return (err = SVRCORE_System_Error);
+ }
+ store->params->type = store->mech->type;
+ store->params->data = (unsigned char *)PORT_Alloc(cryptoMech.ulParameterLen);
+ if (store->params->data == NULL)
+ {
+ return (err = SVRCORE_System_Error);
+ }
+ memcpy(store->params->data, (unsigned char *)cryptoMech.pParameter, cryptoMech.ulParameterLen);
+ store->params->len = cryptoMech.ulParameterLen;
+ PORT_Free(cryptoMech.pParameter);
+ return (err);
+}
+
+static SVRCOREError decryptPassword(struct pk11ContextStore *store, unsigned char *cipher, char **out, int len)
+{
+ SVRCOREError err = SVRCORE_Success;
+ unsigned char *plain = NULL;
+ unsigned char *cipher_with_padding = NULL;
+ SECStatus rv;
+ PK11Context *ctx = 0;
+ int outLen = 0;
+ int blocksize = 0;
+
+ blocksize = slapd_pk11_getBlockSize(store->mech->type, 0);
+ store->length = len;
+
+ /* store->length is the max. length of the returned clear text -
+ must be >= length of crypted bytes - also must be a multiple
+ of blocksize */
+ if (blocksize != 0)
+ {
+ store->length += blocksize - (store->length % blocksize);
+ }
+
+ /* plain will hold the returned clear text */
+ plain = (unsigned char *)slapi_ch_calloc(sizeof(unsigned char),
+ store->length+1);
+ if (!plain)
+ {
+ return (err = SVRCORE_NoMemory_Error);
+ }
+
+ /* create a buffer holding the original cipher bytes, padded with
+ zeros to a multiple of blocksize - do not need +1 since buffer is not
+ a string */
+ cipher_with_padding = (unsigned char *)slapi_ch_calloc(sizeof(unsigned char),
+ store->length);
+ if (!cipher_with_padding)
+ {
+ return (err = SVRCORE_NoMemory_Error);
+ }
+ memcpy(cipher_with_padding, cipher, len);
+
+ ctx = slapd_pk11_createContextBySymKey(store->mech->type, CKA_DECRYPT,
+ store->key, store->params);
+ if (!ctx)
+ {
+ return (err = SVRCORE_System_Error);
+ }
+
+ /* warning - there is a purify UMR in the NSS des code - you may see it when the
+ password is not a multiple of 8 bytes long */
+ rv = slapd_pk11_cipherOp(ctx, plain, &outLen, store->length,
+ cipher_with_padding, store->length);
+ if (rv)
+ {
+ err = SVRCORE_System_Error;
+ }
+
+ rv = slapd_pk11_finalize(ctx);
+ /* we must do the finalize, but we only want to set the err return
+ code if it is not already set */
+ if (rv && (SVRCORE_Success == err))
+ err = SVRCORE_System_Error;
+
+ if (err == SVRCORE_Success)
+ *out = (char *)plain;
+
+ slapi_ch_free((void **)&cipher_with_padding);
+ /* We should free the PK11Context... Something like : */
+ slapd_pk11_destroyContext(ctx, PR_TRUE);
+ return err;
+}
+
+static SVRCOREError cryptPassword(struct pk11ContextStore *store, char * clear, unsigned char **out)
+{
+ SVRCOREError err = SVRCORE_Success;
+ SECStatus rv;
+ PK11Context *ctx = 0;
+ int outLen = 0;
+ int blocksize = 0;
+ unsigned char *clear_with_padding = NULL; /* clear with padding up to blocksize */
+
+ blocksize = slapd_pk11_getBlockSize(store->mech->type, 0);
+ store->length = strlen(clear);
+
+ /* the size of the clear text buffer passed to the des encryption functions
+ must be a multiple of blocksize (usually 8 bytes) - we allocate a buffer
+ of this size, copy the clear text password into it, and pad the rest with
+ zeros */
+ if (blocksize != 0)
+ {
+ store->length += blocksize - (store->length % blocksize);
+ }
+
+ /* store->crypt will hold the crypted password - it must be >= clear length */
+ store->crypt = (unsigned char *)slapi_ch_calloc(sizeof(unsigned char),
+ store->length+1);
+ if (!store->crypt)
+ {
+ return (err = SVRCORE_NoMemory_Error);
+ }
+
+ /* create a buffer big enough to hold the clear text password and padding */
+ clear_with_padding = (unsigned char *)slapi_ch_calloc(sizeof(unsigned char),
+ store->length+1);
+ if (!clear_with_padding)
+ {
+ return (err = SVRCORE_NoMemory_Error);
+ }
+ /* copy the clear text password into the buffer - the calloc insures the
+ remainder is zero padded */
+ strcpy((char *)clear_with_padding, clear);
+
+ ctx = slapd_pk11_createContextBySymKey(store->mech->type, CKA_ENCRYPT,
+ store->key, store->params);
+ if (!ctx)
+ {
+ return (err = SVRCORE_System_Error);
+ }
+
+ rv = slapd_pk11_cipherOp(ctx, store->crypt, &outLen, store->length,
+ clear_with_padding, store->length);
+ if (rv)
+ {
+ err = SVRCORE_System_Error;
+ }
+
+ rv = slapd_pk11_finalize(ctx);
+ /* we must do the finalize, but we only want to set the err return
+ code if it is not already set */
+ if (rv && (SVRCORE_Success == err))
+ err = SVRCORE_System_Error;
+
+ if (err == SVRCORE_Success)
+ *out = store->crypt;
+
+ slapi_ch_free((void **)&clear_with_padding);
+ /* We should free the PK11Context... Something like : */
+ slapd_pk11_destroyContext(ctx, PR_TRUE);
+ return err;
+}
+
+char *
+migrateCredentials(char *oldpath, char *newpath, char *oldcred)
+{
+ char *plain = NULL;
+ char *cipher = NULL;
+
+ init_des_plugin();
+
+ slapd_pk11_configurePKCS11(NULL, NULL, tokDes, ptokDes, NULL, NULL, NULL, NULL, 0, 0 );
+ NSS_NoDB_Init(NULL);
+
+ if ( decode_path(oldcred, &plain, oldpath) == 0 )
+ {
+ if ( encode_path(plain, &cipher, newpath) != 0 )
+ return(NULL);
+ else
+ return(cipher);
+ }
+ else
+ return(NULL);
+}
diff --git a/ldap/servers/plugins/rever/dllmain.c b/ldap/servers/plugins/rever/dllmain.c
new file mode 100644
index 00000000..0bb85815
--- /dev/null
+++ b/ldap/servers/plugins/rever/dllmain.c
@@ -0,0 +1,91 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+ /*
+ * Microsoft Windows specifics for LIBPWDSTORAGE DLL
+ */
+#include "rever.h"
+
+
+#ifdef _WIN32
+/* Lifted from Q125688
+ * How to Port a 16-bit DLL to a Win32 DLL
+ * on the MSVC 4.0 CD
+ */
+BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved)
+{
+
+ switch (fdwReason)
+ {
+ case DLL_PROCESS_ATTACH:
+ /* Code from LibMain inserted here. Return TRUE to keep the
+ DLL loaded or return FALSE to fail loading the DLL.
+
+ You may have to modify the code in your original LibMain to
+ account for the fact that it may be called more than once.
+ You will get one DLL_PROCESS_ATTACH for each process that
+ loads the DLL. This is different from LibMain which gets
+ called only once when the DLL is loaded. The only time this
+ is critical is when you are using shared data sections.
+ If you are using shared data sections for statically
+ allocated data, you will need to be careful to initialize it
+ only once. Check your code carefully.
+
+ Certain one-time initializations may now need to be done for
+ each process that attaches. You may also not need code from
+ your original LibMain because the operating system may now
+ be doing it for you.
+ */
+ /*
+ * 16 bit code calls UnlockData()
+ * which is mapped to UnlockSegment in windows.h
+ * in 32 bit world UnlockData is not defined anywhere
+ * UnlockSegment is mapped to GlobalUnfix in winbase.h
+ * and the docs for both UnlockSegment and GlobalUnfix say
+ * ".. function is oboslete. Segments have no meaning
+ * in the 32-bit environment". So we do nothing here.
+ */
+
+ break;
+
+ case DLL_THREAD_ATTACH:
+ /* Called each time a thread is created in a process that has
+ already loaded (attached to) this DLL. Does not get called
+ for each thread that exists in the process before it loaded
+ the DLL.
+
+ Do thread-specific initialization here.
+ */
+ break;
+
+ case DLL_THREAD_DETACH:
+ /* Same as above, but called when a thread in the process
+ exits.
+
+ Do thread-specific cleanup here.
+ */
+ break;
+
+ case DLL_PROCESS_DETACH:
+ /* Code from _WEP inserted here. This code may (like the
+ LibMain) not be necessary. Check to make certain that the
+ operating system is not doing it for you.
+ */
+
+ break;
+ }
+ /* The return value is only used for DLL_PROCESS_ATTACH; all other
+ conditions are ignored. */
+ return TRUE; /* successful DLL_PROCESS_ATTACH */
+}
+#else
+int CALLBACK
+LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine )
+{
+ /*UnlockData( 0 );*/
+ return( 1 );
+}
+#endif
diff --git a/ldap/servers/plugins/rever/libdes.def b/ldap/servers/plugins/rever/libdes.def
new file mode 100644
index 00000000..048af6f4
--- /dev/null
+++ b/ldap/servers/plugins/rever/libdes.def
@@ -0,0 +1,13 @@
+; BEGIN COPYRIGHT BLOCK
+; Copyright 2001 Sun Microsystems, Inc.
+; Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+; All rights reserved.
+; END COPYRIGHT BLOCK
+;
+DESCRIPTION 'Directory Server 6.2.1 Local Credentials Reversible Encryption Plugin'
+EXPORTS
+ des_cmp @2
+ des_enc @3
+ des_dec @4
+ des_init @5
+ migrateCredentials @6
diff --git a/ldap/servers/plugins/rever/rever.c b/ldap/servers/plugins/rever/rever.c
new file mode 100644
index 00000000..10767944
--- /dev/null
+++ b/ldap/servers/plugins/rever/rever.c
@@ -0,0 +1,77 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include "dirver.h"
+
+#include "rever.h"
+
+static Slapi_PluginDesc pdesc = { "des-storage-scheme", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "DES storage scheme plugin" };
+
+static char *plugin_name = "ReverStoragePlugin";
+
+int
+des_cmp( char *userpwd, char *dbpwd )
+{
+ char *cipher = NULL;
+
+ if ( encode(userpwd, &cipher) != 0 )
+ return 1;
+ else
+ return( strcmp(cipher, dbpwd) );
+}
+
+char *
+des_enc( char *pwd )
+{
+ char *cipher = NULL;
+
+ if ( encode(pwd, &cipher) != 0 )
+ return(NULL);
+ else
+ return( cipher );
+}
+
+char *
+des_dec( char *pwd )
+{
+ char *plain = NULL;
+
+ if ( decode(pwd, &plain) != 0 )
+ return(NULL);
+ else
+ return( plain );
+}
+
+int
+des_init( Slapi_PBlock *pb )
+{
+ int rc;
+ char *name;
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, plugin_name, "=> des_init\n" );
+
+ rc = slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION,
+ (void *) SLAPI_PLUGIN_VERSION_01 );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION,
+ (void *)&pdesc );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_ENC_FN,
+ (void *) des_enc);
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_CMP_FN,
+ (void *) des_cmp );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_DEC_FN,
+ (void *) des_dec );
+ name = slapi_ch_strdup(REVER_SCHEME_NAME);
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_NAME,
+ name );
+
+ init_des_plugin();
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, plugin_name, "<= des_init %d\n\n", rc );
+
+ return( rc );
+}
diff --git a/ldap/servers/plugins/rever/rever.h b/ldap/servers/plugins/rever/rever.h
new file mode 100644
index 00000000..0992aea7
--- /dev/null
+++ b/ldap/servers/plugins/rever/rever.h
@@ -0,0 +1,34 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#ifndef _REVER_H
+#define _REVER_H
+
+#include "slapi-plugin.h"
+#include "nspr.h"
+#include "base64.h"
+#include "slap.h"
+#include "ldaplog.h"
+
+#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */
+
+#define REVER_SCHEME_NAME "DES"
+#define PWD_HASH_PREFIX_START '{'
+#define PWD_HASH_PREFIX_END '}'
+
+
+int rever_cmp( char *userpwd, char *dbpwd );
+char *rever_enc( char *pwd );
+char *rever_dec( char *pwd );
+int rever_init( Slapi_PBlock *pb );
+void init_des_plugin();
+
+int encode(char *inPlain, char ** outCipher);
+int decode(char *inCipher, char **outPlain);
+
+char *migrateCredentials(char *oldpath, char *newpath, char *oldcred);
+typedef char *(*migrate_fn_type)(char *, char *, char *);
+
+#endif
diff --git a/ldap/servers/plugins/roles/Makefile b/ldap/servers/plugins/roles/Makefile
new file mode 100644
index 00000000..2e936fad
--- /dev/null
+++ b/ldap/servers/plugins/roles/Makefile
@@ -0,0 +1,95 @@
+#
+# BEGIN COPYRIGHT BLOCK
+# Copyright 2001 Sun Microsystems, Inc.
+# Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+# All rights reserved.
+# END COPYRIGHT BLOCK
+#
+LDAP_SRC = ../../..
+MCOM_ROOT = ../../../../..
+
+NOSTDCLEAN=true # don't let nsconfig.mk define target clean
+NOSTDSTRIP=true # don't let nsconfig.mk define target strip
+NSPR20=true # probably should be defined somewhere else (not sure where)
+
+OBJDEST = $(OBJDIR)/lib/libroles
+LIBDIR = $(LIB_RELDIR)
+ifndef INSTDIR
+INSTDIR = c:/netscape/server4/
+endif
+
+include $(MCOM_ROOT)/ldapserver/nsdefs.mk
+include $(MCOM_ROOT)/ldapserver/nsconfig.mk
+include $(LDAP_SRC)/nsldap.mk
+
+ifeq ($(ARCH), WINNT)
+DEF_FILE:=./roles.def
+endif
+
+ROLES_OBJS = roles_plugin.o roles_cache.o
+OBJS = $(addprefix $(OBJDEST)/, $(ROLES_OBJS))
+
+ROLES_DLL = roles-plugin
+
+INCLUDES += -I../../slapd -I../../../include
+CFLAGS+=$(SLCFLAGS) -DSLAPD_LOGGING
+
+# DBDB this is clearly all nonsense: the libraries this thing links with should not depend on the platform.
+# However, for now I make this AIX-specific change and leave the NT-specifc stuff in place (I think it came
+# from the makefile I copied to make this one. After build 3, fix this.
+
+ifeq ($(ARCH), WINNT)
+EXTRA_LIBS_DEP += $(LIBSLAPD)
+EXTRA_LIBS += $(NSPRLINK) $(LIBSLAPD) $(LDAP_LIBAVL)
+endif
+
+ifeq ($(ARCH), HPUX)
+EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAPSDK_DEP) $(NSPR_DEP) $(SECURITY_DEP)
+EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(SECURITYLINK) $(NSPRLINK) $(ICULINK)
+endif
+
+ifeq ($(ARCH), WINNT)
+ROLES_DLL_OBJ = $(addprefix $(OBJDEST)/, dllmain.o)
+endif
+
+ifeq ($(ARCH), AIX)
+LD=ld
+EXTRA_LIBS += $(NSPRLINK) $(LIBSLAPD) $(LDAP_LIBAVL)
+endif
+
+ROLES= $(addprefix $(LIBDIR)/, $(ROLES_DLL).$(DLL_SUFFIX))
+
+clientSDK:
+
+all: $(OBJDEST) $(LIBDIR) $(ROLES)
+
+ifeq ($(ARCH), WINNT)
+$(ROLES): $(OBJS) $(ROLES_DLL_OBJ) $(DEF_FILE)
+ $(LINK_DLL) $(ROLES_DLL_OBJ) $(EXTRA_LIBS) /DEF:$(DEF_FILE)
+else
+$(ROLES): $(OBJS) $(ROLES_DLL_OBJ)
+ $(LINK_DLL) $(ROLES_DLL_OBJ) $(EXTRA_LIBS)
+endif
+
+
+veryclean: clean
+
+clean:
+ $(RM) $(OBJS)
+ifeq ($(ARCH), WINNT)
+ $(RM) $(ROLES_DLL_OBJ)
+endif
+ $(RM) $(ROLES)
+
+$(OBJDEST):
+ $(MKDIR) $(OBJDEST)
+
+$(LIBDIR):
+ $(MKDIR) $(LIBDIR)
+
+# Target to push the built binary to an installed server
+#ROLES_PUSH = $(addprefix $(INSTDIR)lib/, $(notdir $(ROLES)))
+#push: $(ROLES_PUSH)
+
+#$(ROLES_PUSH): $(ROLES)
+# cp $(ROLES) $(ROLES_PUSH)
diff --git a/ldap/servers/plugins/roles/dllmain.c b/ldap/servers/plugins/roles/dllmain.c
new file mode 100644
index 00000000..fabf8677
--- /dev/null
+++ b/ldap/servers/plugins/roles/dllmain.c
@@ -0,0 +1,96 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/*
+ * Microsoft Windows specifics for BACK-LDBM DLL
+ */
+#include "ldap.h"
+#include "lber.h"
+
+
+#ifdef _WIN32
+/* Lifted from Q125688
+ * How to Port a 16-bit DLL to a Win32 DLL
+ * on the MSVC 4.0 CD
+ */
+BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved)
+{
+ WSADATA wsadata;
+
+ switch (fdwReason)
+ {
+ case DLL_PROCESS_ATTACH:
+ /* Code from LibMain inserted here. Return TRUE to keep the
+ DLL loaded or return FALSE to fail loading the DLL.
+
+ You may have to modify the code in your original LibMain to
+ account for the fact that it may be called more than once.
+ You will get one DLL_PROCESS_ATTACH for each process that
+ loads the DLL. This is different from LibMain which gets
+ called only once when the DLL is loaded. The only time this
+ is critical is when you are using shared data sections.
+ If you are using shared data sections for statically
+ allocated data, you will need to be careful to initialize it
+ only once. Check your code carefully.
+
+ Certain one-time initializations may now need to be done for
+ each process that attaches. You may also not need code from
+ your original LibMain because the operating system may now
+ be doing it for you.
+ */
+ /*
+ * 16 bit code calls UnlockData()
+ * which is mapped to UnlockSegment in windows.h
+ * in 32 bit world UnlockData is not defined anywhere
+ * UnlockSegment is mapped to GlobalUnfix in winbase.h
+ * and the docs for both UnlockSegment and GlobalUnfix say
+ * ".. function is oboslete. Segments have no meaning
+ * in the 32-bit environment". So we do nothing here.
+ */
+
+ if( errno = WSAStartup(0x0101, &wsadata ) != 0 )
+ return FALSE;
+
+ break;
+
+ case DLL_THREAD_ATTACH:
+ /* Called each time a thread is created in a process that has
+ already loaded (attached to) this DLL. Does not get called
+ for each thread that exists in the process before it loaded
+ the DLL.
+
+ Do thread-specific initialization here.
+ */
+ break;
+
+ case DLL_THREAD_DETACH:
+ /* Same as above, but called when a thread in the process
+ exits.
+
+ Do thread-specific cleanup here.
+ */
+ break;
+
+ case DLL_PROCESS_DETACH:
+ /* Code from _WEP inserted here. This code may (like the
+ LibMain) not be necessary. Check to make certain that the
+ operating system is not doing it for you.
+ */
+ WSACleanup();
+
+ break;
+ }
+ /* The return value is only used for DLL_PROCESS_ATTACH; all other
+ conditions are ignored. */
+ return TRUE; // successful DLL_PROCESS_ATTACH
+}
+#else
+int CALLBACK
+LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine )
+{
+ /*UnlockData( 0 );*/
+ return( 1 );
+}
+#endif
diff --git a/ldap/servers/plugins/roles/roles.def b/ldap/servers/plugins/roles/roles.def
new file mode 100644
index 00000000..0ae9d85f
--- /dev/null
+++ b/ldap/servers/plugins/roles/roles.def
@@ -0,0 +1,10 @@
+; BEGIN COPYRIGHT BLOCK
+; Copyright 2001 Sun Microsystems, Inc.
+; Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+; All rights reserved.
+; END COPYRIGHT BLOCK
+;
+DESCRIPTION 'Netscape Directory Server 7.0 Roles Plugin'
+EXPORTS
+ roles_init @2
+ plugin_init_debug_level @3
diff --git a/ldap/servers/plugins/roles/roles_cache.c b/ldap/servers/plugins/roles/roles_cache.c
new file mode 100644
index 00000000..f4dc31c2
--- /dev/null
+++ b/ldap/servers/plugins/roles/roles_cache.c
@@ -0,0 +1,2061 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include "portable.h"
+#include "slapi-plugin.h"
+#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */
+#include "dirver.h"
+
+/* This is naughty ... */
+#include "slapi-private.h"
+
+/* include NSPR header files */
+#include "slap.h"
+#include "prthread.h"
+#include "prlock.h"
+#include "prerror.h"
+#include "prcvar.h"
+#include "prio.h"
+#include "avl.h"
+#include "vattr_spi.h"
+#include "roles_cache.h"
+#include "views.h"
+
+#ifdef SOLARIS
+#include <tnf/probe.h>
+#else
+#define TNF_PROBE_0(a,b,c)
+#endif
+
+#define MAX_NESTED_ROLES 30
+
+static char *allUserAttributes[] = {
+ LDAP_ALL_USER_ATTRS,
+ NULL
+};
+
+/* views scoping */
+static void **views_api;
+
+/* Service provider handler */
+static vattr_sp_handle *vattr_handle = NULL;
+
+/* List of nested roles */
+typedef struct _role_object_nested {
+ Slapi_DN *dn; /* value of attribute nsroledn in a nested role definition */
+} role_object_nested;
+
+/* Role object structure */
+typedef struct _role_object {
+ Slapi_DN *dn; /* dn of a role entry */
+ int type; /* ROLE_TYPE_MANAGED|ROLE_TYPE_FILTERED|ROLE_TYPE_NESTED */
+ Slapi_Filter *filter; /* if ROLE_TYPE_FILTERED */
+ Avlnode *avl_tree; /* if ROLE_TYPE_NESTED: tree of nested DNs (avl_data is a role_object_nested struct) */
+} role_object;
+
+/* Structure containing the roles definitions for a given suffix */
+typedef struct _roles_cache_def {
+
+ /* Suffix DN*/
+ Slapi_DN *suffix_dn;
+
+ /* Module level thread control */
+ PRThread *roles_tid;
+ int keeprunning;
+
+ Slapi_Mutex *cache_lock;
+ Slapi_Mutex *stop_lock;
+
+ Slapi_Mutex *change_lock;
+ Slapi_CondVar *something_changed;
+
+ Slapi_Mutex *create_lock;
+ Slapi_CondVar *suffix_created;
+ int is_ready;
+
+ /* Root of the avl tree containing all the roles definitions
+ NB: avl_data field is of type role_object
+ */
+ Avlnode *avl_tree;
+
+ /* Next roles suffix definitions */
+ struct _roles_cache_def *next;
+
+ /* Info passed from the server when an notification is sent to the plugin */
+ char *notified_dn;
+ Slapi_Entry *notified_entry;
+ int notified_operation;
+
+} roles_cache_def;
+
+
+/* Global list containing all the roles definitions per suffix */
+static roles_cache_def *roles_list = NULL;
+
+static PRRWLock *global_lock = NULL;
+
+/* Structure holding the nsrole values */
+typedef struct _roles_cache_build_result
+{
+ Slapi_ValueSet **nsrole_values; /* nsrole computed values */
+ Slapi_Entry *requested_entry; /* entry to get nsrole from */
+ int has_value; /* flag to determine if a new value has been added to the result */
+ int need_value; /* flag to determine if we need the result */
+} roles_cache_build_result;
+
+/* Structure used to check if is_entry_member_of is part of a role defined in its suffix */
+typedef struct _roles_cache_search_in_nested
+{
+ Slapi_Entry *is_entry_member_of;
+ int present; /* flag to know if the entry is part of a role */
+ int hint; /* to check the depth of the nested */
+} roles_cache_search_in_nested;
+
+/* Structure used to handle roles searches */
+typedef struct _roles_cache_search_roles
+{
+ roles_cache_def *suffix_def;
+ int rc; /* to check the depth of the nested */
+} roles_cache_search_roles;
+
+static roles_cache_def* roles_cache_create_suffix(Slapi_DN *sdn);
+static int roles_cache_add_roles_from_suffix(Slapi_DN *suffix_dn, roles_cache_def *suffix_def);
+static void roles_cache_wait_on_change(void * arg);
+static void roles_cache_trigger_update_suffix(void *handle, char *be_name, int old_be_state, int new_be_state);
+static void roles_cache_trigger_update_role(char *dn, Slapi_Entry *role_entry, Slapi_DN *be_dn, int operation);
+static int roles_cache_update(roles_cache_def *suffix_to_update);
+static int roles_get_roles_from_entry(Slapi_DN * suffix, Slapi_PBlock **int_search_pb);
+static int roles_cache_create_role_under(roles_cache_def** roles_cache_suffix, Slapi_Entry *entry);
+static int roles_cache_create_object_from_entry(Slapi_Entry *role_entry, role_object **result, int hint);
+static int roles_cache_determine_class(Slapi_Entry *role_entry);
+static int roles_cache_node_cmp( caddr_t d1, caddr_t d2 );
+static int roles_cache_insert_object(Avlnode **tree, role_object *object);
+static int roles_cache_node_nested_cmp( caddr_t d1, caddr_t d2 );
+static int roles_cache_insert_object_nested(Avlnode **tree, role_object_nested *object);
+static int roles_cache_object_nested_from_dn(Slapi_DN *role_dn, role_object_nested **result);
+static int roles_cache_build_nsrole( caddr_t data, caddr_t arg );
+static int roles_cache_find_node( caddr_t d1, caddr_t d2 );
+static int roles_cache_find_roles_in_suffix(Slapi_DN *target_entry_dn, roles_cache_def **list_of_roles);
+static int roles_is_entry_member_of_object(caddr_t data, caddr_t arg );
+static int roles_check_managed(Slapi_Entry *entry_to_check, role_object *role, int *present);
+static int roles_check_filtered(Slapi_Entry *entry_to_check, role_object *role, int *present);
+static int roles_check_nested(caddr_t data, caddr_t arg);
+static int roles_is_inscope(Slapi_Entry *entry_to_check, Slapi_DN *role_dn);
+static void berval_set_string(struct berval *bv, const char* string);
+static void roles_cache_role_def_delete(roles_cache_def *role_def);
+static void roles_cache_role_def_free(roles_cache_def *role_def);
+static void roles_cache_role_object_free(role_object *this_role);
+static void roles_cache_role_object_nested_free(role_object_nested *this_role);
+static int roles_cache_dump( caddr_t data, caddr_t arg );
+static int roles_cache_add_entry_cb(Slapi_Entry* e, void *callback_data);
+static void roles_cache_result_cb( int rc, void *callback_data);
+static Slapi_DN* roles_cache_get_top_suffix(Slapi_DN *suffix);
+
+/* ============== FUNCTIONS ================ */
+
+/* roles_cache_init
+ ----------------
+ create the cache for all the existing suffixes
+ starts up the threads which wait for changes
+ also registers vattr callbacks
+
+ return 0 if OK
+ return -1 otherwise
+*/
+int roles_cache_init()
+{
+ int rc = 0;
+ void *node = NULL;
+ Slapi_DN *sdn = NULL;
+ roles_cache_def *new_suffix = NULL;
+
+ slapi_log_error(SLAPI_LOG_PLUGIN,
+ ROLES_PLUGIN_SUBSYSTEM, "--> roles_cache_init\n");
+
+ if ( global_lock == NULL )
+ {
+ global_lock = PR_NewRWLock(0,"roles_cache");
+ }
+
+ /* grab the views interface */
+ if(slapi_apib_get_interface(Views_v1_0_GUID, &views_api))
+ {
+ /* lets be tolerant if views is disabled */
+ views_api = 0;
+ }
+
+ /* For each top suffix, get the roles definitions defined below it */
+ PR_RWLock_Wlock(global_lock);
+
+ sdn = slapi_get_first_suffix(&node, 0);
+ while (sdn)
+ {
+
+ if ( (new_suffix = roles_cache_create_suffix(sdn)) == NULL )
+ {
+ PR_DestroyRWLock(global_lock);
+ global_lock = NULL;
+ return(-1);
+ }
+
+ if ( roles_cache_add_roles_from_suffix(sdn, new_suffix) != 0 )
+ {
+ /* No roles in that suffix, stop the thread and remove it ? */
+ }
+ sdn = slapi_get_next_suffix(&node, 0);
+ }
+ PR_RWLock_Unlock(global_lock);
+
+ /* to expose roles_check to ACL plugin */
+ slapi_register_role_check(roles_check);
+
+ /* Register a callback on backends creation|modification|deletion,
+ so that we update the corresponding cache */
+ slapi_register_backend_state_change(NULL, roles_cache_trigger_update_suffix);
+
+ if ( slapi_vattrspi_register((vattr_sp_handle **)&vattr_handle,
+ roles_sp_get_value,
+ roles_sp_compare_value,
+ roles_sp_list_types) )
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, ROLES_PLUGIN_SUBSYSTEM,
+ "roles_cache_init: slapi_vattrspi_register failed\n");
+
+ PR_DestroyRWLock(global_lock);
+ global_lock = NULL;
+ return(-1);
+ }
+ else if ( slapi_vattrspi_regattr(vattr_handle,NSROLEATTR,"", NULL) )
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, ROLES_PLUGIN_SUBSYSTEM,
+ "roles_cache_init: slapi_vattrspi_regattr failed\n");
+ free(vattr_handle);
+ PR_DestroyRWLock(global_lock);
+ global_lock = NULL;
+ return(-1);
+ }
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "<-- roles_cache_init\n");
+ return rc;
+}
+
+/* roles_cache_create_suffix
+ -------------------------
+ Create a new entry in the global list
+ return a pointer on the suffix stucture: OK
+ return NULL: fail
+ */
+static roles_cache_def *roles_cache_create_suffix(Slapi_DN *sdn)
+{
+ roles_cache_def *current_suffix = NULL;
+ roles_cache_def *new_suffix = NULL;
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "--> roles_cache_create_suffix\n");
+
+ /* Allocate a new suffix block */
+ new_suffix = (roles_cache_def*)slapi_ch_calloc(1, sizeof(roles_cache_def));
+ if ( new_suffix == NULL )
+ {
+ slapi_log_error(SLAPI_LOG_FATAL,
+ ROLES_PLUGIN_SUBSYSTEM,
+ "roles_cache_create_suffix: Unable to allocate memory, cannot create role cache\n");
+ return(NULL);
+ }
+
+ new_suffix->cache_lock = slapi_new_mutex();
+ new_suffix->change_lock = slapi_new_mutex();
+ new_suffix->stop_lock = slapi_new_mutex();
+ new_suffix->create_lock = slapi_new_mutex();
+ if ( new_suffix->stop_lock == NULL ||
+ new_suffix->change_lock == NULL ||
+ new_suffix->cache_lock == NULL ||
+ new_suffix->create_lock == NULL )
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, ROLES_PLUGIN_SUBSYSTEM,
+ "roles_cache_create_suffix: Lock creation failed\n");
+ roles_cache_role_def_free(new_suffix);
+ return(NULL);
+ }
+
+ new_suffix->something_changed = slapi_new_condvar(new_suffix->change_lock);
+ if ( new_suffix->something_changed == NULL)
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, ROLES_PLUGIN_SUBSYSTEM,
+ "roles_cache_create_suffix: Lock creation failed\n");
+ roles_cache_role_def_free(new_suffix);
+ return(NULL);
+ }
+
+ new_suffix->suffix_created = slapi_new_condvar(new_suffix->create_lock);
+ if ( new_suffix->suffix_created == NULL)
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, ROLES_PLUGIN_SUBSYSTEM,
+ "roles_cache_create_suffix: Lock creation failed\n");
+ roles_cache_role_def_free(new_suffix);
+ return(NULL);
+ }
+
+ new_suffix->keeprunning = 1;
+
+ new_suffix->suffix_dn = slapi_sdn_dup(sdn);
+
+ /* those 3 items are used to give back info to the thread when
+ it is awakened */
+ new_suffix->notified_dn = NULL;
+ new_suffix->notified_entry = NULL;
+ new_suffix->notified_operation = 0;
+
+ /* Create the global list */
+ if ( roles_list == NULL )
+ {
+ roles_list = new_suffix;
+ }
+ else
+ {
+ current_suffix = roles_list;
+ while ( current_suffix != NULL )
+ {
+ if ( current_suffix->next == NULL )
+ {
+ current_suffix->next = new_suffix;
+ break;
+ }
+ else
+ {
+ current_suffix = current_suffix->next;
+ }
+ }
+ }
+
+ /* to prevent deadlock */
+ new_suffix->is_ready = 0;
+ if ( (new_suffix->roles_tid = PR_CreateThread (PR_USER_THREAD,
+ roles_cache_wait_on_change,
+ (void*)new_suffix,
+ PR_PRIORITY_NORMAL,
+ PR_GLOBAL_THREAD,
+ PR_UNJOINABLE_THREAD,
+ SLAPD_DEFAULT_THREAD_STACKSIZE)) == NULL )
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, ROLES_PLUGIN_SUBSYSTEM,
+ "roles_cache_create_suffix: PR_CreateThread failed\n");
+ roles_cache_role_def_delete(new_suffix);
+ return(NULL);
+ }
+
+ slapi_lock_mutex(new_suffix->create_lock);
+ if (new_suffix->is_ready != 1)
+ {
+ slapi_wait_condvar(new_suffix->suffix_created, NULL);
+ }
+ slapi_unlock_mutex(new_suffix->create_lock);
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "<-- roles_cache_create_suffix\n");
+ return(new_suffix);
+}
+
+/* roles_cache_wait_on_change
+ --------------------------
+ Sit around waiting on a notification that something has
+ changed, then fires off the updates
+ */
+static void roles_cache_wait_on_change(void * arg)
+{
+ roles_cache_def *roles_def = (roles_cache_def*)arg;
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "--> roles_cache_wait_on_change\n");
+
+ slapi_lock_mutex(roles_def->stop_lock);
+ slapi_lock_mutex(roles_def->change_lock);
+
+ while ( roles_def->keeprunning)
+ {
+ slapi_unlock_mutex(roles_def->change_lock);
+ slapi_lock_mutex(roles_def->change_lock);
+
+ /* means that the thread corresponding to that suffix is ready to receive notifications
+ from the server */
+ slapi_lock_mutex(roles_def->create_lock);
+ if ( roles_def->is_ready == 0 )
+ {
+ slapi_notify_condvar( roles_def->suffix_created, 1 );
+ roles_def->is_ready = 1;
+ }
+ slapi_unlock_mutex(roles_def->create_lock);
+
+ /* XXX In case the BE containing this role_def signaled
+ a shut down between the unlock/lock above should
+ test roles_def->keeprunning before
+ going to sleep.
+ */
+ slapi_wait_condvar(roles_def->something_changed, NULL);
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "roles_cache_wait_on_change \n");
+
+ if ( roles_def->keeprunning )
+ {
+ roles_cache_update(roles_def);
+ }
+ }
+
+ /* shut down the cache */
+ slapi_unlock_mutex(roles_def->change_lock);
+ slapi_unlock_mutex(roles_def->stop_lock);
+
+ roles_cache_role_def_free(roles_def);
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "<-- roles_cache_wait_on_change thread exit\n");
+}
+
+/* roles_cache_trigger_update_suffix
+ --------------------------------
+ This is called when a backend changes state (created|modified|deleted)
+ We simply signal to update the associated role cache in this case
+ */
+static void roles_cache_trigger_update_suffix(void *handle, char *be_name, int old_be_state, int new_be_state)
+{
+ roles_cache_def *current_role = roles_list;
+ const Slapi_DN *be_suffix_dn = NULL;
+ Slapi_DN *top_suffix_dn = NULL;
+ Slapi_Backend *backend = NULL;
+ int found = 0;
+
+ PR_RWLock_Wlock(global_lock);
+
+ if ( (new_be_state == SLAPI_BE_STATE_DELETE) || (new_be_state == SLAPI_BE_STATE_OFFLINE) )
+ {
+ /* Invalidate and rebuild the whole cache */
+ roles_cache_def *current_role = NULL;
+ roles_cache_def *next_role = NULL;
+ Slapi_DN *sdn = NULL;
+ void *node = NULL;
+ roles_cache_def *new_suffix = NULL;
+
+ /* Go through all the roles list and trigger the associated structure */
+ current_role = roles_list;
+ while ( current_role )
+ {
+ slapi_lock_mutex(current_role->change_lock);
+ current_role->keeprunning = 0;
+ next_role = current_role->next;
+ slapi_notify_condvar(current_role->something_changed, 1 );
+ slapi_unlock_mutex(current_role->change_lock);
+
+ current_role = next_role;
+ }
+
+ /* rebuild a new one */
+ roles_list = NULL;
+
+ sdn = slapi_get_first_suffix(&node, 0);
+ while (sdn)
+ {
+
+ if ( (new_suffix = roles_cache_create_suffix(sdn)) == NULL )
+ {
+ PR_RWLock_Unlock(global_lock);
+ return;
+ }
+
+ roles_cache_add_roles_from_suffix(sdn, new_suffix);
+ sdn = slapi_get_next_suffix(&node, 0);
+ }
+ PR_RWLock_Unlock(global_lock);
+ return;
+ }
+
+ /* Backend back on line or new one created*/
+ backend = slapi_be_select_by_instance_name(be_name);
+ if ( backend != NULL )
+ {
+ be_suffix_dn = slapi_be_getsuffix(backend, 0);
+ top_suffix_dn = roles_cache_get_top_suffix((Slapi_DN *)be_suffix_dn);
+ }
+
+ while ( (current_role != NULL) && !found && (top_suffix_dn != NULL) )
+ {
+ /* The backend already exists (back online): so invalidate "old roles definitions" */
+ if ( slapi_sdn_compare(current_role->suffix_dn, top_suffix_dn) == 0 )
+ {
+ roles_cache_role_def_delete(current_role);
+ found = 1;
+ }
+ else
+ {
+ current_role = current_role->next;
+ }
+ }
+
+ if ( top_suffix_dn != NULL )
+ {
+ /* Add the new definitions in the cache */
+ roles_cache_def *new_suffix = roles_cache_create_suffix(top_suffix_dn);
+
+ if ( new_suffix != NULL )
+ {
+ roles_cache_add_roles_from_suffix(top_suffix_dn, new_suffix);
+ }
+ slapi_sdn_free(&top_suffix_dn);
+ }
+
+ PR_RWLock_Unlock(global_lock);
+}
+
+/* roles_cache_trigger_update_role
+ --------------------------------
+ Call when an entry containing a role definition has been added, modified
+ or deleted
+ */
+static void roles_cache_trigger_update_role(char *dn, Slapi_Entry *roles_entry, Slapi_DN *be_dn, int operation)
+{
+ int found = 0;
+ roles_cache_def *current_role = NULL;
+
+ PR_RWLock_Wlock(global_lock);
+
+ current_role = roles_list;
+
+ slapi_log_error( SLAPI_LOG_PLUGIN,
+ ROLES_PLUGIN_SUBSYSTEM, "--> roles_cache_trigger_update_role: %x \n", roles_list);
+
+ /* Go through all the roles list and trigger the associated structure */
+
+ /* be_dn is already the top suffix for that dn */
+ while ( (current_role != NULL) && !found )
+ {
+ if ( slapi_sdn_compare(current_role->suffix_dn, be_dn) == 0 )
+ {
+ found = 1;
+ }
+ else
+ {
+ current_role = current_role->next;
+ }
+ }
+
+ if ( found )
+ {
+ slapi_lock_mutex(current_role->change_lock);
+
+ slapi_entry_free (current_role->notified_entry);
+ current_role->notified_entry = roles_entry;
+ slapi_ch_free ((void**)&(current_role->notified_dn));
+ current_role->notified_dn = dn;
+ current_role->notified_operation = operation;
+
+ roles_cache_update(current_role);
+
+ slapi_unlock_mutex(current_role->change_lock);
+ }
+
+ PR_RWLock_Unlock(global_lock);
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "<-- roles_cache_trigger_update_role: %x \n", roles_list);
+}
+
+/* roles_cache_update
+ ------------------
+ Update the cache associated to a suffix
+ Return 0: ok
+ Return -1: fail
+ */
+static int roles_cache_update(roles_cache_def *suffix_to_update)
+{
+ int rc = 0;
+ int operation;
+ Slapi_Entry *entry = NULL;
+ Slapi_DN *dn = NULL;
+ role_object *to_delete = NULL;
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "--> roles_cache_update \n");
+
+ slapi_lock_mutex(suffix_to_update->cache_lock);
+
+ operation = suffix_to_update->notified_operation;
+ entry = suffix_to_update->notified_entry;
+ dn = slapi_sdn_new();
+ slapi_sdn_set_dn_byval(dn, suffix_to_update->notified_dn);
+
+ if ( (entry != NULL) && (dn != NULL) )
+ {
+ if ( (operation == SLAPI_OPERATION_MODIFY) ||
+ (operation == SLAPI_OPERATION_DELETE) )
+ {
+ /* delete it */
+ int dummy;
+
+ to_delete = (role_object *)avl_delete(&(suffix_to_update->avl_tree), dn, roles_cache_find_node, &dummy);
+ roles_cache_role_object_free(to_delete);
+ to_delete = NULL;
+ if ( slapi_is_loglevel_set(SLAPI_LOG_PLUGIN) )
+ {
+ avl_apply(suffix_to_update->avl_tree, (IFP)roles_cache_dump, &rc, -1, AVL_INORDER);
+ }
+
+ }
+ if ( (operation == SLAPI_OPERATION_MODIFY) ||
+ (operation ==SLAPI_OPERATION_ADD) )
+ {
+ rc = roles_cache_create_role_under(&suffix_to_update,entry);
+ }
+ if ( entry != NULL )
+ {
+ slapi_entry_free(entry);
+ }
+ suffix_to_update->notified_entry = NULL;
+
+ }
+ slapi_unlock_mutex(suffix_to_update->cache_lock);
+
+ if ( dn != NULL )
+ {
+ slapi_sdn_free(&dn);
+ }
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "<-- roles_cache_update \n");
+ return(rc);
+}
+
+/* roles_cache_stop
+ ----------------
+
+ XXX the stop_lock of a roles_cache_def
+ doesn't seem to serve any useful purpose...
+
+ */
+void roles_cache_stop()
+{
+ roles_cache_def *current_role = NULL;
+ roles_cache_def *next_role = NULL;
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "--> roles_cache_stop\n");
+
+ /* Go through all the roles list and trigger the associated structure */
+ PR_RWLock_Wlock(global_lock);
+ current_role = roles_list;
+ while ( current_role )
+ {
+ slapi_lock_mutex(current_role->change_lock);
+ current_role->keeprunning = 0;
+ next_role = current_role->next;
+ slapi_notify_condvar(current_role->something_changed, 1 );
+ slapi_unlock_mutex(current_role->change_lock);
+
+ current_role = next_role;
+ }
+ PR_RWLock_Unlock(global_lock);
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "<-- roles_cache_stop\n");
+}
+
+/* roles_cache_is_role_entry
+ -------------------------
+ Check if the entry is a role
+ return -1: error in processing
+ return 0: entry is not a role
+ return 1: entry is a role
+*/
+static int roles_cache_is_role_entry(struct slapi_entry *entry)
+{
+ Slapi_Attr *pObjclasses = NULL;
+ Slapi_Value *val = NULL;
+ char *pObj = NULL;
+ int index = 0;
+
+ int nsroledefinition = 0;
+ int nsrolesimpleOrComplex = 0;
+ int nsroletype = 0;
+
+ if ( entry == NULL )
+ {
+ return(0);
+ }
+
+ if ( slapi_entry_attr_find(entry, "objectclass", &pObjclasses) )
+ {
+ slapi_log_error( SLAPI_LOG_FATAL,
+ ROLES_PLUGIN_SUBSYSTEM,
+ "roles_cache_is_role_entry: failed to get objectclass from %s\n",slapi_entry_get_dn_const(entry));
+ return(-1);
+ }
+
+ /* Check out the object classes to see if this was a nsroledefinition */
+
+ val = 0;
+ index = slapi_attr_first_value( pObjclasses, &val );
+ while(val)
+ {
+ const char *p;
+ int len = 0;
+
+ pObj = (char*)slapi_value_get_string(val);
+
+ for ( p = pObj, len = 0;
+ (*p != '\0') && (*p != ' ');
+ p++, len++ )
+ {
+ ; /* NULL */
+ }
+
+ if ( !strncasecmp(pObj, (char*)"nsroledefinition", len) )
+ {
+ nsroledefinition = 1;
+ }
+ if ( !strncasecmp(pObj, (char*)"nssimpleroledefinition", len) ||
+ !strncasecmp(pObj, (char*)"nscomplexroledefinition", len) )
+ {
+ nsrolesimpleOrComplex = 1;
+ }
+ if( !strncasecmp(pObj, (char*)"nsmanagedroledefinition", len) ||
+ !strncasecmp(pObj, (char*)"nsfilteredroledefinition", len) ||
+ !strncasecmp(pObj, (char*)"nsnestedroledefinition", len)
+ )
+ {
+ nsroletype = 1;
+ }
+ index = slapi_attr_next_value( pObjclasses, index, &val );
+ }
+ if ( (nsroledefinition == 0) ||
+ (nsrolesimpleOrComplex == 0) ||
+ (nsroletype == 0) )
+ {
+ return(0);
+ }
+ return(1);
+}
+
+/* roles_cache_change_notify
+ -------------------------
+ determines if the change effects the cache and if so
+ signals a rebuild
+ -- called when modify|modrdn|add|delete operation is performed --
+ -- called from a postoperation on an entry
+ XXX this implies that the client may have already received his LDAP response,
+ but that there will be a delay before he sees the effect in the roles cache.
+ Should we be doing this processing in a BE_POST_MODIFY postop
+ which is called _before_ the response goes to the client ?
+*/
+void roles_cache_change_notify(Slapi_PBlock *pb)
+{
+ char *dn = NULL;
+ struct slapi_entry *e = NULL;
+ struct slapi_entry *pre = NULL;
+ struct slapi_entry *entry = NULL;
+ Slapi_Backend *be = NULL;
+ Slapi_Operation *pb_operation = NULL;
+ int operation;
+ int do_update = 0;
+ int rc = -1;
+
+ slapi_log_error(SLAPI_LOG_PLUGIN,
+ ROLES_PLUGIN_SUBSYSTEM,
+ "--> roles_cache_change_notify\n");
+
+ /* if the current operation has failed, don't even try the post operation */
+ slapi_pblock_get( pb, SLAPI_PLUGIN_OPRETURN, &rc );
+ if ( rc != LDAP_SUCCESS )
+ {
+ return;
+ }
+
+ /* Don't update local cache when remote entries are updated */
+ slapi_pblock_get( pb, SLAPI_BACKEND, &be );
+ if ( ( be!=NULL ) &&
+ ( slapi_be_is_flag_set(be,SLAPI_BE_FLAG_REMOTE_DATA)) )
+ {
+ return;
+ }
+
+ slapi_pblock_get(pb, SLAPI_TARGET_DN, &dn);
+ if( dn == NULL )
+ {
+ return;
+ }
+
+ slapi_pblock_get (pb, SLAPI_OPERATION, &pb_operation);
+ operation = operation_get_type(pb_operation);
+
+ switch (operation)
+ {
+ case SLAPI_OPERATION_DELETE:
+
+ slapi_pblock_get(pb, SLAPI_ENTRY_PRE_OP, &e);
+ if( e == NULL )
+ {
+ return;
+ }
+ break;
+
+ case SLAPI_OPERATION_ADD:
+ slapi_pblock_get(pb, SLAPI_ENTRY_POST_OP, &e);
+ if ( e == NULL )
+ {
+ return;
+ }
+ break;
+
+ case SLAPI_OPERATION_MODIFY:
+ case SLAPI_OPERATION_MODRDN:
+ /* those operations are treated the same way and modify is a deletion followed by an addition.
+ the only point to take care is that dn is the olddn */
+ operation = SLAPI_OPERATION_MODIFY;
+ slapi_pblock_get(pb, SLAPI_ENTRY_PRE_OP, &pre);
+ if( pre == NULL )
+ {
+ return;
+ }
+ slapi_pblock_get(pb, SLAPI_ENTRY_POST_OP, &e);
+ if ( e == NULL )
+ {
+ return;
+ }
+ break;
+ default:
+ slapi_log_error( SLAPI_LOG_FATAL,
+ ROLES_PLUGIN_SUBSYSTEM,
+ "roles_cache_change_notify: unknown operation %d\n",operation);
+ return;
+ }
+
+ if ( operation != SLAPI_OPERATION_MODIFY )
+ {
+ if ( roles_cache_is_role_entry(e) != 1 )
+ {
+ slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "<-- roles_cache_change_notify: not a role entry\n");
+ return;
+ }
+ entry = slapi_entry_dup(e);
+ do_update = 1;
+ }
+ else
+ {
+ int is_pre_role = roles_cache_is_role_entry(pre);
+ int is_post_role = roles_cache_is_role_entry(e);
+ if ( (is_pre_role==1) && (is_post_role==1) ) /* role definition has changed */
+ {
+ entry = slapi_entry_dup(e);
+ do_update = 1;
+ }
+ else if ( is_pre_role == 1 ) /* entry is no more a role */
+ {
+ operation = SLAPI_OPERATION_DELETE;
+ do_update = 1;
+ }
+ else if ( is_post_role == 1 ) /* entry is now a role */
+ {
+ operation = SLAPI_OPERATION_ADD;
+ entry = slapi_entry_dup(e);
+ do_update = 1;
+ }
+ else
+ {
+ slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "<-- roles_cache_change_notify: not a role entry\n");
+ return;
+ }
+ }
+
+ if ( do_update )
+ {
+#ifdef moretrace
+if ( e != NULL )
+{
+ Slapi_Attr *attr = NULL;
+ int rc;
+
+ /* Get the list of nested roles */
+ rc = slapi_entry_attr_find(e,ROLE_NESTED_ATTR_NAME,&attr);
+
+ if ( (rc == 0) && attr)
+ {
+ /* Recurse to get the definition objects for them */
+ Slapi_Value **va = attr_get_present_values(attr);
+ int i = 0;
+ char *string = NULL;
+
+ for ( i = 0; va[i] != NULL; i++ )
+ {
+ string = (char*)slapi_value_get_string(va[i]);
+ slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "roles_cache_change_notify:%s\n",string);
+ }
+ }
+}
+#endif
+ Slapi_DN *top_suffix = roles_cache_get_top_suffix(*(be->be_suffix));
+
+ if ( top_suffix != NULL )
+ {
+ roles_cache_trigger_update_role( slapi_ch_strdup(dn), entry,
+ top_suffix,
+ operation);
+
+ slapi_sdn_free(&top_suffix);
+ }
+
+ }
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "<-- roles_cache_change_notify\n");
+
+}
+
+/* roles_cache_get_top_suffix
+ -------------------------
+ The returned Slapi_DN must be freed with slapi_sdn_free().
+*/
+static Slapi_DN* roles_cache_get_top_suffix(Slapi_DN *suffix)
+{
+ Slapi_DN *current_suffix = NULL;
+ Slapi_DN parent_suffix;
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "--> roles_cache_get_top_suffix\n");
+
+ if ( suffix == NULL )
+ {
+ return(NULL);
+ }
+ current_suffix = slapi_sdn_new();
+ slapi_sdn_init(&parent_suffix);
+
+ /* we must get the top suffix for that DN */
+ slapi_sdn_copy(suffix,current_suffix);
+ while ( !slapi_sdn_isempty(current_suffix) )
+ {
+ if ( slapi_is_root_suffix(current_suffix) != 1 )
+ {
+ slapi_sdn_get_parent(current_suffix,&parent_suffix);
+ slapi_sdn_copy(&parent_suffix, current_suffix);
+ }
+ else
+ {
+ slapi_sdn_done(&parent_suffix);
+ return(current_suffix);
+ }
+ }
+ /* we should not return that way ... */
+ slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "<-- roles_cache_get_top_suffix\n");
+ slapi_sdn_done(&parent_suffix);
+ slapi_sdn_free(&current_suffix);
+ return(NULL);
+}
+
+/* roles_cache_add_roles_from_suffix
+ -------------------------------
+ Get the roles entries under the suffix
+ return 0: OK
+ return -1: this suffix has no role defined
+ */
+static int roles_cache_add_roles_from_suffix(Slapi_DN *suffix_dn, roles_cache_def *suffix_def)
+{
+ /* Search subtree-level under this entry */
+ int rc = -1;
+ roles_cache_search_roles info;
+ Slapi_PBlock *int_search_pb = NULL;
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "--> roles_get_roles_from_entry\n");
+
+ info.suffix_def = suffix_def;
+ info.rc = LDAP_NO_SUCH_OBJECT;
+
+ /* Get the roles definitions of the given suffix_dn */
+ int_search_pb = slapi_pblock_new ();
+ slapi_search_internal_set_pb(int_search_pb,
+ (char*)slapi_sdn_get_ndn(suffix_dn),
+ LDAP_SCOPE_SUBTREE,
+ ROLE_DEFINITION_FILTER,
+ allUserAttributes,
+ 0 /* attrsonly */,
+ NULL /* controls */,
+ NULL /* uniqueid */,
+ roles_get_plugin_identity(),
+ SLAPI_OP_FLAG_NEVER_CHAIN /* actions : get local roles only */ );
+
+ slapi_search_internal_callback_pb(int_search_pb,
+ &info /* callback_data */,
+ roles_cache_result_cb,
+ roles_cache_add_entry_cb,
+ NULL /* referral_callback */);
+
+ slapi_pblock_destroy (int_search_pb);
+ int_search_pb = NULL;
+
+ if ( info.rc == LDAP_SUCCESS )
+ {
+ rc = 0;
+ }
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "<-- roles_get_roles_from_entry\n");
+
+ return(rc);
+}
+
+/* roles_cache_add_entry_cb
+ -----------------------
+*/
+static int roles_cache_add_entry_cb(Slapi_Entry* e, void *callback_data)
+{
+ roles_cache_search_roles *info=(roles_cache_search_roles *)callback_data;
+
+ roles_cache_def *suffix = info->suffix_def;
+
+ roles_cache_create_role_under(&suffix, e);
+ return(0);
+}
+
+/* roles_cache_result_cb
+ -----------------------
+*/
+static void roles_cache_result_cb( int rc, void *callback_data) {
+ roles_cache_search_roles *info=(roles_cache_search_roles *)callback_data;
+
+ info->rc = rc;
+}
+
+
+/* roles_cache_create_role_under
+ ----------------------------
+ Create the avl tree of roles definitions defined in the scope
+ of the suffix
+ Return 0: OK
+ Return -1: fail
+*/
+static int roles_cache_create_role_under(roles_cache_def** roles_cache_suffix, Slapi_Entry *entry)
+{
+ int rc;
+ role_object *new_role = NULL;
+
+ slapi_log_error(SLAPI_LOG_PLUGIN,
+ ROLES_PLUGIN_SUBSYSTEM, "--> roles_cache_create_role_under: %s - %x\n",
+ slapi_sdn_get_dn((*roles_cache_suffix)->suffix_dn),
+ (*roles_cache_suffix)->avl_tree);
+
+ rc = roles_cache_create_object_from_entry(entry,&new_role,0);
+ slapi_log_error(SLAPI_LOG_PLUGIN,
+ ROLES_PLUGIN_SUBSYSTEM,
+ "roles_cache_create_role_under: create node for entry %s - rc: %d SUFFIX: %x\n",
+ slapi_entry_get_dn_const(entry), rc, (*roles_cache_suffix)->avl_tree);
+
+ if ( (rc == 0) && new_role)
+ {
+ /* Add to the tree where avl_data is a role_object struct */
+ rc = roles_cache_insert_object(&((*roles_cache_suffix)->avl_tree),new_role);
+ slapi_log_error(SLAPI_LOG_PLUGIN,
+ ROLES_PLUGIN_SUBSYSTEM, "roles_cache_create_role_under:%s in tree %x rc: %d\n",
+ (char*)slapi_sdn_get_ndn(new_role->dn),
+ (*roles_cache_suffix)->avl_tree, rc);
+ }
+ return(rc);
+}
+
+
+/* roles_cache_create_object_from_entry
+ ------------------------------------
+ Create a node role_object from the information contained in role_entry
+ Return 0
+ Return ENOMEM: fail
+ Return SLAPI_ROLE_DEFINITION_ERROR: fail
+ Return SLAPI_ROLE_ERROR_NO_FILTER_SPECIFIED: fail
+ Return SLAPI_ROLE_ERROR_FILTER_BAD: fail
+*/
+static int roles_cache_create_object_from_entry(Slapi_Entry *role_entry, role_object **result, int hint)
+{
+ int rc = 0;
+ int type = 0;
+ role_object *this_role = NULL;
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM,
+ "--> roles_cache_create_object_from_entry\n");
+
+ *result = NULL;
+
+ /* Do not allow circular dependencies */
+ if ( hint > MAX_NESTED_ROLES )
+ {
+ char *ndn = NULL;
+
+ ndn = slapi_entry_get_ndn( role_entry );
+ slapi_log_error(
+ SLAPI_LOG_FATAL,
+ ROLES_PLUGIN_SUBSYSTEM,
+ "Maximum roles nesting exceeded (%d), not retrieving roles from entry %s--probable circular definition\n",
+ MAX_NESTED_ROLES,
+ ndn);
+
+ return (0);
+ }
+
+ /* Create the role cache definition */
+ this_role = (role_object*)slapi_ch_calloc(1, sizeof(role_object));
+ if (this_role == NULL )
+ {
+ return ENOMEM;
+ }
+
+ /* Check the entry is OK */
+ /* Determine role type and assign to structure */
+ /* We determine the role type by reading the objectclass */
+ if ( roles_cache_is_role_entry(role_entry) == 0 )
+ {
+ /* Bad type */
+ slapi_ch_free((void**)&this_role);
+ return SLAPI_ROLE_DEFINITION_ERROR;
+ }
+
+ type = roles_cache_determine_class(role_entry);
+
+ if (type != 0)
+ {
+ this_role->type = type;
+ }
+ else
+ {
+ /* Bad type */
+ slapi_ch_free((void**)&this_role);
+ return SLAPI_ROLE_DEFINITION_ERROR;
+ }
+
+ this_role->dn = slapi_sdn_new();
+ slapi_sdn_copy(slapi_entry_get_sdn(role_entry),this_role->dn);
+
+ /* Depending upon role type, pull out the remaining information we need */
+ switch (this_role->type)
+ {
+ case ROLE_TYPE_MANAGED:
+
+ /* Nothing further needed */
+ break;
+
+ case ROLE_TYPE_FILTERED:
+ {
+
+ Slapi_Filter *filter = NULL;
+ char *filter_attr_value = NULL;
+
+ /* Get the filter and retrieve the filter attribute */
+ filter_attr_value = slapi_entry_attr_get_charptr(role_entry,ROLE_FILTER_ATTR_NAME);
+ if ( filter_attr_value == NULL )
+ {
+ /* Means probably no attribute or no value there */
+ slapi_ch_free((void**)&this_role);
+ return SLAPI_ROLE_ERROR_NO_FILTER_SPECIFIED;
+ }
+
+ /* Turn it into a slapi filter object */
+ filter = slapi_str2filter(filter_attr_value);
+ slapi_ch_free((void**)&filter_attr_value);
+
+ if ( filter == NULL )
+ {
+ /* An error has occured */
+ slapi_ch_free((void**)&this_role);
+ return SLAPI_ROLE_ERROR_FILTER_BAD;
+ }
+ /* Store on the object */
+ this_role->filter = filter;
+
+ break;
+ }
+
+ case ROLE_TYPE_NESTED:
+ {
+ Slapi_Attr *attr = NULL;
+
+ /* Get the list of nested roles */
+ rc = slapi_entry_attr_find(role_entry,ROLE_NESTED_ATTR_NAME,&attr);
+
+ if ( (rc == 0) && attr)
+ {
+ /* Recurse to get the definition objects for them */
+ Slapi_Value **va = attr_get_present_values(attr);
+ int i = 0;
+ char *string = NULL;
+ Slapi_DN nested_role_dn;
+ role_object_nested *nested_role_object = NULL;
+
+ for ( i = 0; va[i] != NULL; i++ )
+ {
+ string = (char*)slapi_value_get_string(va[i]);
+
+ /* Make a DN from the string */
+ slapi_sdn_init_dn_byref(&nested_role_dn,string);
+
+ slapi_log_error(SLAPI_LOG_PLUGIN,
+ ROLES_PLUGIN_SUBSYSTEM, "roles_cache_create_object_from_entry: dn %s, nested %s\n",
+ (char*)slapi_sdn_get_ndn(this_role->dn),string);
+
+ /* Make a role object nested from the DN */
+ rc = roles_cache_object_nested_from_dn(&nested_role_dn,&nested_role_object);
+
+ /* Insert it into the nested list */
+ if ( (rc == 0) && nested_role_object)
+ {
+ /* Add to the tree where avl_data is a role_object_nested struct */
+ rc = roles_cache_insert_object_nested(&(this_role->avl_tree),nested_role_object);
+ }
+ slapi_sdn_done(&nested_role_dn);
+ }
+ }
+
+ break;
+ }
+
+ default:
+ slapi_log_error(SLAPI_LOG_FATAL,
+ ROLES_PLUGIN_SUBSYSTEM, "wrong role type\n");
+ }
+
+ if ( rc == 0 )
+ {
+ *result = this_role;
+ }
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM,
+ "<-- roles_cache_create_object_from_entry\n");
+
+
+ return rc;
+}
+
+/* roles_cache_determine_class:
+ ----------------------------
+ Determine the type of role depending on the objectclass
+ Return the type of the role
+ */
+static int roles_cache_determine_class(Slapi_Entry *role_entry)
+{
+ /* Examine the entry's objectclass attribute */
+ int found_managed = 0;
+ int found_filtered = 0;
+ int found_nested = 0;
+ Slapi_Attr *attr= NULL;
+ struct berval bv = {0};
+ int rc = 0;
+ int type = 0;
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM,
+ "--> roles_cache_determine_class\n");
+
+ rc = slapi_entry_attr_find(role_entry,"objectclass",&attr);
+ if ( rc != 0 )
+ {
+ /* No objectclass, definitely an error */
+ return 0;
+ }
+
+ berval_set_string(&bv,ROLE_OBJECTCLASS_MANAGED);
+ rc = slapi_attr_value_find(attr,&bv);
+ if ( rc == 0 )
+ {
+ found_managed = 1;
+ type = ROLE_TYPE_MANAGED;
+ }
+
+ berval_set_string(&bv,ROLE_OBJECTCLASS_FILTERED);
+ rc = slapi_attr_value_find(attr,&bv);
+ if ( rc == 0 )
+ {
+ found_filtered = 1;
+ type = ROLE_TYPE_FILTERED;
+ }
+
+ berval_set_string(&bv,ROLE_OBJECTCLASS_NESTED);
+ rc = slapi_attr_value_find(attr,&bv);
+ if ( rc == 0 )
+ {
+ found_nested = 1;
+ type = ROLE_TYPE_NESTED;
+ }
+
+ if ( (found_managed + found_nested + found_filtered) > 1 )
+ {
+ /* Means some goofball configured a role definition which is trying to be more than one different type. error. */
+ return 0;
+ }
+
+ if ( (found_managed + found_nested + found_filtered) == 0)
+ {
+ /* Means this entry isn't any of the role types we handle. error. */
+ return 0;
+ }
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM,
+ "<-- roles_cache_determine_class\n");
+
+ /* Return the appropriate type ordinal */
+ return type;
+}
+
+/* roles_cache_node_cmp:
+ ---------------------
+ Comparison function to add a new node in the avl tree (avl_data is of type role_object)
+ */
+static int roles_cache_node_cmp( caddr_t d1, caddr_t d2 )
+{
+ role_object *role_to_insert = (role_object*)d1;
+ role_object *current_role = (role_object*)d2;
+
+ /* role_to_insert and current_role are never NULL in that context */
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM,
+ "roles_cache_node_cmp\n");
+
+ return (slapi_sdn_compare((Slapi_DN *)role_to_insert->dn, (Slapi_DN *)current_role->dn));
+}
+
+/* roles_cache_insert_object:
+ --------------------------
+ Insert a new node in the avl tree of a specific suffix
+ */
+static int roles_cache_insert_object(Avlnode **tree, role_object *object)
+{
+
+ slapi_log_error(SLAPI_LOG_PLUGIN,
+ ROLES_PLUGIN_SUBSYSTEM, "roles_cache_insert_object: %s in tree %x\n",
+ (char*)slapi_sdn_get_ndn(object->dn),
+ *tree);
+ return (avl_insert(tree, (caddr_t)object, roles_cache_node_cmp, avl_dup_error));
+}
+
+/* roles_cache_node_nested_cmp:
+ ----------------------------
+ Comparison function to add a new node in the avl tree
+ */
+static int roles_cache_node_nested_cmp( caddr_t d1, caddr_t d2 )
+{
+ role_object_nested *role_to_insert = (role_object_nested*)d1;
+ role_object_nested *current_role = (role_object_nested*)d2;
+
+ /* role_to_insert and current_role are never NULL in that context */
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM,
+ "roles_cache_node_nested_cmp\n");
+
+ return slapi_sdn_compare(role_to_insert->dn, current_role->dn);
+}
+
+/* roles_cache_insert_object_nested:
+ ---------------------------------
+ Insert a new node in the avl tree of a specific suffix
+ */
+static int roles_cache_insert_object_nested(Avlnode **tree, role_object_nested *object)
+{
+ slapi_log_error(SLAPI_LOG_PLUGIN,
+ ROLES_PLUGIN_SUBSYSTEM, "roles_cache_insert_object_nested: %s in tree %x: \n",
+ (char*)slapi_sdn_get_ndn(object->dn), *tree);
+
+ return (avl_insert(tree, (caddr_t)object, roles_cache_node_nested_cmp, avl_dup_error));
+}
+
+/* roles_cache_object_nested_from_dn
+ ----------------------------------
+ Get the role associated to an entry DN
+ Return 0: OK
+ Return ENOMEM: fail
+ */
+static int roles_cache_object_nested_from_dn(Slapi_DN *role_dn, role_object_nested **result)
+{
+ role_object_nested *nested_role = NULL;
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM,
+ "--> roles_cache_object_nested_from_dn\n");
+
+ *result = NULL;
+
+ /* Create the role cache definition */
+ nested_role = (role_object_nested*)slapi_ch_calloc(1, sizeof(role_object_nested));
+ if (nested_role == NULL )
+ {
+ return ENOMEM;
+ }
+
+ nested_role->dn = slapi_sdn_new();
+ slapi_sdn_copy(role_dn,nested_role->dn);
+ *result = nested_role;
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM,
+ "<-- roles_cache_object_nested_from_dn\n");
+ return 0;
+}
+
+/* roles_cache_listroles
+ --------------------
+ Lists all the roles an entry posesses
+ return_values = 0 means that we don't need the nsrole values
+ return_values = 1 means that we need the nsrole values
+ Return 0: the entry has nsrole
+ Return -1: the entry has no nsrole
+ */
+int roles_cache_listroles(Slapi_Entry *entry, int return_values, Slapi_ValueSet **valueset_out)
+{
+ roles_cache_def *roles_cache = NULL;
+ role_object *this_role = NULL;
+ int rc = 0;
+ Avlnode * tree = NULL;
+ roles_cache_build_result arg;
+ Slapi_Backend *backend = NULL;
+
+ slapi_log_error(SLAPI_LOG_PLUGIN,
+ ROLES_PLUGIN_SUBSYSTEM, "--> roles_cache_listroles\n");
+
+ backend = slapi_mapping_tree_find_backend_for_sdn(slapi_entry_get_sdn(entry));
+ if ( (backend != NULL) && slapi_be_is_flag_set(backend,SLAPI_BE_FLAG_REMOTE_DATA) )
+ {
+ /* the entry is not local, so don't return anything */
+ return (-1);
+ }
+
+ if ( return_values )
+ {
+ *valueset_out = (Slapi_ValueSet*)slapi_ch_calloc(1,sizeof(Slapi_ValueSet));
+ slapi_valueset_init(*valueset_out);
+ }
+
+ /* First get a list of all the in-scope roles */
+ /* XXX really need a mutex for this read operation ? */
+ PR_RWLock_Rlock(global_lock);
+
+ rc = roles_cache_find_roles_in_suffix( slapi_entry_get_sdn(entry),&roles_cache);
+
+ PR_RWLock_Unlock(global_lock);
+
+ /* Traverse the tree checking if the entry has any of the roles */
+ if ( roles_cache != NULL )
+ {
+ if ( roles_cache->avl_tree )
+ {
+ arg.nsrole_values = valueset_out;
+ arg.need_value = return_values;
+ arg.requested_entry = entry;
+ arg.has_value = 0;
+
+ /* XXX really need a mutex for this read operation ? */
+ slapi_lock_mutex(roles_cache->cache_lock);
+
+ avl_apply(roles_cache->avl_tree, (IFP)roles_cache_build_nsrole, &arg, -1, AVL_INORDER);
+
+ slapi_unlock_mutex(roles_cache->cache_lock);
+
+ if( !arg.has_value )
+ {
+ if ( return_values )
+ {
+ slapi_valueset_free(*valueset_out);
+ *valueset_out = NULL;
+ }
+ rc = -1;
+ }
+ /* Free the list (we already did that) */
+ }
+ else
+ {
+ if ( return_values )
+ {
+ slapi_valueset_free(*valueset_out);
+ *valueset_out = NULL;
+ }
+ rc = -1;
+ }
+ }
+ else
+ {
+ /* no roles associated */
+ rc = -1;
+ }
+ slapi_log_error(SLAPI_LOG_PLUGIN,
+ ROLES_PLUGIN_SUBSYSTEM, "<-- roles_cache_listroles\n");
+ return rc;
+}
+
+/* roles_cache_build_nsrole
+ ------------------------
+ Traverse the tree containing roles definitions for a suffix and for each
+ one of them, check wether the entry is a member of it or not
+ For ones which check out positive, we add their DN to the value
+ always return 0 to allow to trverse all the tree
+ */
+static int roles_cache_build_nsrole( caddr_t data, caddr_t arg )
+{
+ Slapi_Value *value = NULL;
+ roles_cache_build_result *result = (roles_cache_build_result*)arg;
+ role_object *this_role = (role_object*)data;
+ roles_cache_search_in_nested get_nsrole;
+ /* Return a value different from the stop flag to be able
+ to go through all the tree */
+ int rc = 0;
+
+ slapi_log_error(SLAPI_LOG_PLUGIN,
+ ROLES_PLUGIN_SUBSYSTEM, "--> roles_cache_build_nsrole: role %s\n",
+ (char*) slapi_sdn_get_ndn(this_role->dn));
+
+ value = slapi_value_new_string("");
+
+ get_nsrole.is_entry_member_of = result->requested_entry;
+ get_nsrole.present = 0;
+ get_nsrole.hint = 0;
+
+ roles_is_entry_member_of_object((caddr_t)this_role, (caddr_t)&get_nsrole);
+
+ /* If so, add its DN to the attribute */
+ if (get_nsrole.present)
+ {
+ result->has_value = 1;
+ if ( result->need_value )
+ {
+ slapi_value_set_string(value,(char*) slapi_sdn_get_ndn(this_role->dn));
+ slapi_valueset_add_value(*(result->nsrole_values),value);
+ }
+ else
+ {
+ /* we don't need the value but we already know there is one nsrole.
+ stop the traversal
+ */
+ rc = -1;
+ }
+ }
+
+ slapi_value_free(&value);
+
+ slapi_log_error(SLAPI_LOG_PLUGIN,
+ ROLES_PLUGIN_SUBSYSTEM, "<-- roles_cache_build_nsrole\n");
+
+ return rc;
+}
+
+
+/* roles_check
+ -----------
+ Checks if an entry has a presented role, assuming that we've already verified
+that
+ the role both exists and is in scope
+ return 0: no processing error
+ return -1: error
+ */
+int roles_check(Slapi_Entry *entry_to_check, Slapi_DN *role_dn, int *present)
+{
+ roles_cache_def *roles_cache = NULL;
+ role_object *this_role = NULL;
+ roles_cache_search_in_nested get_nsrole;
+
+ int rc = 0;
+
+ slapi_log_error(SLAPI_LOG_PLUGIN,
+ ROLES_PLUGIN_SUBSYSTEM, "--> roles_check\n");
+
+ *present = 0;
+
+ PR_RWLock_Rlock(global_lock);
+
+ if ( roles_cache_find_roles_in_suffix(slapi_entry_get_sdn(entry_to_check),
+ &roles_cache) != 0 )
+ {
+ PR_RWLock_Unlock(global_lock);
+ return -1;
+ }
+ PR_RWLock_Unlock(global_lock);
+
+ this_role = (role_object *)avl_find(roles_cache->avl_tree, role_dn, (IFP)roles_cache_find_node);
+
+ /* MAB: For some reason the assumption made by this function (the role exists and is in scope)
+ * does not seem to be true... this_role might be NULL after the avl_find call (is the avl_tree
+ * broken? Anyway, this is crashing the 5.1 server on 29-Aug-01, so I am applying the following patch
+ * to avoid the crash inside roles_is_entry_member_of_object */
+ /* Begin patch */
+ if (!this_role) {
+ /* Assume that the entry is not member of the role (*present=0) and leave... */
+ return rc;
+ }
+ /* End patch */
+
+ get_nsrole.is_entry_member_of = entry_to_check;
+ get_nsrole.present = 0;
+ get_nsrole.hint = 0;
+
+ roles_is_entry_member_of_object((caddr_t)this_role, (caddr_t)&get_nsrole);
+ *present = get_nsrole.present;
+
+ slapi_log_error(SLAPI_LOG_PLUGIN,
+ ROLES_PLUGIN_SUBSYSTEM, "<-- roles_check\n");
+
+ return rc;
+}
+
+/* roles_cache_find_node:
+ ---------------------
+ Comparison function to add a new node in the avl tree
+ */
+static int roles_cache_find_node( caddr_t d1, caddr_t d2 )
+{
+ Slapi_DN *data = (Slapi_DN *)d1;
+ role_object *role= (role_object *)d2;
+
+ /* role is not NULL in that context */
+
+ slapi_log_error(SLAPI_LOG_PLUGIN,
+ ROLES_PLUGIN_SUBSYSTEM, "roles_cache_find_node: %s %s\n",
+ slapi_sdn_get_dn(data), slapi_sdn_get_dn(role->dn));
+
+ return (slapi_sdn_compare(data, (Slapi_DN *)role->dn));
+}
+
+/* roles_cache_find_roles_in_suffix
+ -------------------------------
+ Find all the roles in scope to an entry
+ */
+static int roles_cache_find_roles_in_suffix(Slapi_DN *target_entry_dn, roles_cache_def **list_of_roles)
+{
+ int rc = -1;
+ Slapi_Backend *backend = NULL;
+
+ slapi_log_error(SLAPI_LOG_PLUGIN,
+ ROLES_PLUGIN_SUBSYSTEM, "--> roles_cache_find_roles_in_suffix\n");
+
+ *list_of_roles = NULL;
+ backend = slapi_mapping_tree_find_backend_for_sdn(target_entry_dn);
+ if ( (backend != NULL) && !slapi_be_is_flag_set(backend,SLAPI_BE_FLAG_REMOTE_DATA) )
+ {
+ Slapi_DN *suffix = roles_cache_get_top_suffix(*(backend->be_suffix));
+ roles_cache_def *current_role = roles_list;
+
+ /* Go through all the roles list and trigger the associated structure */
+ while ( (current_role != NULL) && (suffix != NULL) )
+ {
+ if ( slapi_sdn_compare(current_role->suffix_dn, suffix) == 0 )
+ {
+ *list_of_roles = current_role;
+ /* OK, we have found one */
+ slapi_sdn_free(&suffix);
+ return 0;
+ }
+ else
+ {
+ current_role = current_role->next;
+ }
+ }
+ if ( suffix != NULL )
+ {
+ slapi_sdn_free(&suffix);
+ }
+ /* If we got out that way, means that we didn't have find
+ roles definitions for that suffix */
+ return rc;
+ }
+
+ slapi_log_error(SLAPI_LOG_PLUGIN,
+ ROLES_PLUGIN_SUBSYSTEM, "<-- roles_cache_find_roles_in_suffix\n");
+ return rc;
+}
+
+/* roles_is_entry_member_of_object
+ --------------------------------
+ Check if the entry is part of a role defined in its suffix
+ return 0: ok
+ return 1: fail
+ -> to check the presence, see present
+ */
+static int roles_is_entry_member_of_object(caddr_t data, caddr_t argument )
+{
+ int rc = -1;
+
+ roles_cache_search_in_nested *get_nsrole = (roles_cache_search_in_nested*)argument;
+ role_object *this_role = (role_object*)data;
+
+ Slapi_Entry *entry_to_check = get_nsrole->is_entry_member_of;
+
+ slapi_log_error(SLAPI_LOG_PLUGIN,
+ ROLES_PLUGIN_SUBSYSTEM, "--> roles_is_entry_member_of_object\n");
+
+ if (!roles_is_inscope(entry_to_check, this_role->dn))
+ {
+ slapi_log_error(SLAPI_LOG_PLUGIN,
+ ROLES_PLUGIN_SUBSYSTEM, "roles_is_entry_member_of_object-> entry not in scope of role\n");
+ return rc;
+ }
+
+ if ( this_role != NULL )
+ {
+ /* Determine the role type */
+ switch (this_role->type)
+ {
+ case ROLE_TYPE_MANAGED:
+ rc = roles_check_managed(entry_to_check,this_role,&get_nsrole->present);
+ break;
+ case ROLE_TYPE_FILTERED:
+ rc = roles_check_filtered(entry_to_check,this_role,&get_nsrole->present);
+ break;
+ case ROLE_TYPE_NESTED:
+ {
+ /* Go through the tree of the nested DNs */
+ get_nsrole->hint++;
+ avl_apply(this_role->avl_tree, (IFP)roles_check_nested, get_nsrole, 0, AVL_INORDER);
+ get_nsrole->hint--;
+
+ /* kexcoff?? */
+ rc = get_nsrole->present;
+ break;
+ }
+ default:
+ slapi_log_error(SLAPI_LOG_FATAL,
+ ROLES_PLUGIN_SUBSYSTEM, "roles_is_entry_member_of_object-> invalid role type\n");
+ }
+ }
+
+ slapi_log_error(SLAPI_LOG_PLUGIN,
+ ROLES_PLUGIN_SUBSYSTEM, "<-- roles_is_entry_member_of_object\n");
+ return rc;
+}
+
+/* roles_check_managed
+ -------------------------
+ Check a managed role: we just need to check the content of the entry's nsRoleDN attribute
+ against the role DN
+ return 0: ok
+ return 1: fail
+ -> to check the presence, see present
+ */
+static int roles_check_managed(Slapi_Entry *entry_to_check, role_object *role, int *present)
+{
+ int rc = 0;
+ Slapi_Attr *attr = NULL;
+
+ slapi_log_error(SLAPI_LOG_PLUGIN,
+ ROLES_PLUGIN_SUBSYSTEM, "--> roles_check_managed\n");
+ /* Get the attribute */
+ rc = slapi_entry_attr_find(entry_to_check,ROLE_MANAGED_ATTR_NAME,&attr);
+
+ if ( rc == 0)
+ {
+ struct berval bv = {0};
+ char *dn_string = NULL;
+
+ /* Check content against the presented DN */
+ /* We assume that this function handles normalization and so on */
+ dn_string = (char*) slapi_sdn_get_ndn(role->dn);
+ berval_set_string(&bv,dn_string);
+ rc = slapi_attr_value_find(attr,&bv);
+ if ( rc == 0 )
+ {
+ *present = 1;
+ }
+ }
+ slapi_log_error(SLAPI_LOG_PLUGIN,
+ ROLES_PLUGIN_SUBSYSTEM,
+ "<-- roles_check_managed: entry %s role %s present %d\n",
+ slapi_entry_get_dn_const(entry_to_check),(char*)slapi_sdn_get_ndn(role->dn),*present);
+ return rc;
+}
+
+/* roles_check_filtered
+ --------------------------
+ Check a filtered role: call slapi_filter_test here on the entry
+ and the filter from the role object
+ return 0: ok
+ return 1: fail
+ -> to check the presence, see present
+ */
+static int roles_check_filtered(Slapi_Entry *entry_to_check, role_object *role, int *present)
+{
+ int rc = 0;
+
+ slapi_log_error(SLAPI_LOG_PLUGIN,
+ ROLES_PLUGIN_SUBSYSTEM, "--> roles_check_filtered\n");
+ rc = slapi_filter_test_simple(entry_to_check,role->filter);
+ if ( rc == 0 )
+ {
+ *present = 1;
+ }
+ slapi_log_error(SLAPI_LOG_PLUGIN,
+ ROLES_PLUGIN_SUBSYSTEM,
+ "<-- roles_check_filtered: entry %s role %s present %d\n",
+ slapi_entry_get_dn_const(entry_to_check),(char*)slapi_sdn_get_ndn(role->dn),*present);
+ return rc;
+}
+
+
+/* roles_check_nested
+ ------------------------
+ Check a nested role
+ return 0: ok
+ return -1: fail
+ -> to check the presence, see present
+ */
+static int roles_check_nested(caddr_t data, caddr_t arg)
+{
+ roles_cache_search_in_nested *get_nsrole = (roles_cache_search_in_nested*)arg;
+ int rc = -1;
+ role_object_nested *current_nested_role = (role_object_nested*)data;
+
+
+ /* do not allow circular dependencies, the cheap and easy way */
+ if( get_nsrole->hint > MAX_NESTED_ROLES)
+ {
+ char *ndn = NULL;
+
+ ndn = slapi_entry_get_ndn( get_nsrole->is_entry_member_of );
+ slapi_log_error(SLAPI_LOG_FATAL,
+ ROLES_PLUGIN_SUBSYSTEM,
+ "Maximum roles nesting exceeded (max %d current %d), not checking roles in entry %s--probable circular definition\n",
+ MAX_NESTED_ROLES,
+ get_nsrole->hint,
+ ndn);
+
+ /* Stop traversal value */
+ return 0;
+ }
+
+ /* Here we traverse the list of nested roles, calling the appropriate
+ evaluation function for those in turn */
+
+ if (current_nested_role)
+ {
+ roles_cache_def *roles_cache = NULL;
+ role_object *this_role = NULL;
+
+ slapi_log_error(SLAPI_LOG_PLUGIN,
+ ROLES_PLUGIN_SUBSYSTEM,
+ "-->roles_check_nested: entry %s role %s present %d\n",
+ slapi_entry_get_dn_const(get_nsrole->is_entry_member_of),
+ (char*)slapi_sdn_get_ndn(current_nested_role->dn),
+ get_nsrole->present);
+
+ if ( roles_cache_find_roles_in_suffix(current_nested_role->dn,
+ &roles_cache) != 0 )
+ {
+ return rc;
+ }
+
+ if ( slapi_is_loglevel_set(SLAPI_LOG_PLUGIN) )
+ {
+ avl_apply(roles_cache->avl_tree, (IFP)roles_cache_dump, &rc, -1, AVL_INORDER);
+ }
+
+ this_role = (role_object *)avl_find(roles_cache->avl_tree,
+ current_nested_role->dn,
+ (IFP)roles_cache_find_node);
+
+ if ( this_role == NULL )
+ {
+ /* the nested role doesn't exist */
+ slapi_log_error(SLAPI_LOG_FATAL,
+ ROLES_PLUGIN_SUBSYSTEM,
+ "The nested role %s doesn't exist\n",
+ (char*)slapi_sdn_get_ndn(current_nested_role->dn));
+ return rc;
+ }
+ /* get the role_object data associated to that dn */
+ if ( roles_is_inscope(get_nsrole->is_entry_member_of, this_role->dn) )
+ {
+ /* The list of nested roles is contained in the role definition */
+ roles_is_entry_member_of_object((caddr_t)this_role, (caddr_t)get_nsrole);
+ if ( get_nsrole->present == 1 )
+ {
+ return 0;
+ }
+ }
+ }
+ slapi_log_error(SLAPI_LOG_PLUGIN,
+ ROLES_PLUGIN_SUBSYSTEM, "<-- roles_check_nested\n");
+ return rc;
+}
+
+/* roles_is_inscope
+ ----------------------
+ Tells us if a presented role is in scope with respect to the presented entry
+ */
+static int roles_is_inscope(Slapi_Entry *entry_to_check, Slapi_DN *role_dn)
+{
+ int rc;
+
+ Slapi_DN role_parent;
+
+ slapi_log_error(SLAPI_LOG_PLUGIN,
+ ROLES_PLUGIN_SUBSYSTEM, "--> roles_is_inscope\n");
+
+ slapi_sdn_init(&role_parent);
+ slapi_sdn_get_parent(role_dn,&role_parent);
+
+ rc = slapi_sdn_scope_test(slapi_entry_get_sdn( entry_to_check ),
+ &role_parent,
+ LDAP_SCOPE_SUBTREE);
+ /* we need to check whether the entry would be returned by a view in scope */
+ if(!rc && views_api)
+ {
+ rc = views_entry_exists(views_api, (char*)slapi_sdn_get_ndn(&role_parent), entry_to_check);
+ }
+
+ slapi_sdn_done(&role_parent);
+
+
+ slapi_log_error(SLAPI_LOG_PLUGIN,
+ ROLES_PLUGIN_SUBSYSTEM, "<-- roles_is_inscope: entry %s role %s result %d\n",
+ slapi_entry_get_dn_const(entry_to_check),(char*)slapi_sdn_get_ndn(role_dn), rc);
+
+ return (rc);
+}
+
+static void berval_set_string(struct berval *bv, const char* string)
+{
+ bv->bv_len= strlen(string);
+ bv->bv_val= (void*)string; /* We cast away the const, but we're not going to change anything
+*/
+}
+
+/* roles_cache_role_def_delete
+ ----------------------------
+*/
+static void roles_cache_role_def_delete(roles_cache_def *role_def)
+{
+ roles_cache_def *current = roles_list;
+ roles_cache_def *previous = NULL;
+
+ slapi_log_error(SLAPI_LOG_PLUGIN,
+ ROLES_PLUGIN_SUBSYSTEM, "--> roles_cache_role_def_delete\n");
+
+ while ( current!= NULL )
+ {
+ if ( slapi_sdn_compare(current->suffix_dn, role_def->suffix_dn) == 0 )
+ {
+ if ( previous== NULL )
+ {
+ roles_list = current->next;
+ }
+ else
+ {
+ previous->next = current->next;
+ }
+ slapi_lock_mutex(role_def->change_lock);
+ role_def->keeprunning = 0;
+ slapi_notify_condvar(role_def->something_changed, 1 );
+ slapi_unlock_mutex(role_def->change_lock);
+ break;
+ }
+ else
+ {
+ previous = current;
+ current = current->next;
+ }
+ }
+ slapi_log_error(SLAPI_LOG_PLUGIN,
+ ROLES_PLUGIN_SUBSYSTEM, "<-- roles_cache_role_def_delete\n");
+}
+
+/* roles_cache_role_def_free
+ ----------------------------
+*/
+static void roles_cache_role_def_free(roles_cache_def *role_def)
+{
+ roles_cache_def *next_def = NULL;
+
+ slapi_log_error(SLAPI_LOG_PLUGIN,
+ ROLES_PLUGIN_SUBSYSTEM, "--> roles_cache_role_def_free\n");
+ if ( role_def == NULL )
+ {
+ return;
+ }
+
+ slapi_lock_mutex(role_def->stop_lock);
+
+ avl_free(role_def->avl_tree, (IFP)roles_cache_role_object_free);
+ slapi_sdn_free(&(role_def->suffix_dn));
+ slapi_destroy_mutex(role_def->cache_lock);
+ role_def->cache_lock = NULL;
+ slapi_destroy_mutex(role_def->change_lock);
+ role_def->change_lock = NULL;
+ slapi_destroy_condvar(role_def->something_changed);
+ slapi_destroy_mutex(role_def->create_lock);
+ role_def->create_lock = NULL;
+ slapi_destroy_condvar(role_def->suffix_created);
+
+ slapi_ch_free((void**)&role_def->notified_dn);
+ if ( role_def->notified_entry != NULL )
+ {
+ slapi_entry_free(role_def->notified_entry);
+ }
+
+ slapi_unlock_mutex(role_def->stop_lock);
+ slapi_destroy_mutex(role_def->stop_lock);
+ role_def->stop_lock = NULL;
+
+ slapi_ch_free((void**)&role_def);
+
+ slapi_log_error(SLAPI_LOG_PLUGIN,
+ ROLES_PLUGIN_SUBSYSTEM, "<-- roles_cache_role_def_free\n");
+}
+
+/* roles_cache_role_object_free
+ ----------------------------
+*/
+static void roles_cache_role_object_free(role_object *this_role)
+{
+ slapi_log_error(SLAPI_LOG_PLUGIN,
+ ROLES_PLUGIN_SUBSYSTEM, "--> roles_cache_role_object_free\n");
+
+ if ( this_role == NULL )
+ {
+ return;
+ }
+
+ switch (this_role->type)
+ {
+ case ROLE_TYPE_MANAGED:
+ /* Nothing further needed */
+ break;
+ case ROLE_TYPE_FILTERED:
+ /* Free the filter */
+ if (this_role->filter)
+ {
+ slapi_filter_free(this_role->filter,1);
+ this_role->filter = NULL;
+ }
+ break;
+ case ROLE_TYPE_NESTED:
+ /* Free the list of nested roles */
+ {
+ avl_free(this_role->avl_tree, roles_cache_role_object_nested_free);
+ }
+ break;
+ }
+
+ slapi_sdn_free(&this_role->dn);
+
+ /* Free the object */
+ slapi_ch_free((void**)&this_role);
+
+ slapi_log_error(SLAPI_LOG_PLUGIN,
+ ROLES_PLUGIN_SUBSYSTEM, "<-- roles_cache_role_object_free\n");
+}
+
+/* roles_cache_role_object_nested_free
+ ------------------------------------
+*/
+static void roles_cache_role_object_nested_free(role_object_nested *this_role)
+{
+ slapi_log_error(SLAPI_LOG_PLUGIN,
+ ROLES_PLUGIN_SUBSYSTEM, "--> roles_cache_role_object_nested_free\n");
+
+ if ( this_role == NULL )
+ {
+ return;
+ }
+
+ slapi_sdn_free(&this_role->dn);
+
+ /* Free the object */
+ slapi_ch_free((void**)&this_role);
+
+ slapi_log_error(SLAPI_LOG_PLUGIN,
+ ROLES_PLUGIN_SUBSYSTEM, "<-- roles_cache_role_object_nested_free\n");
+}
+
+static int roles_cache_dump( caddr_t data, caddr_t arg )
+{
+ role_object *this_role = (role_object*)data;
+
+ slapi_log_error(SLAPI_LOG_PLUGIN,
+ ROLES_PLUGIN_SUBSYSTEM, "roles_cache_dump: %x - %s - %x\n",
+ this_role, (char*)slapi_sdn_get_ndn(this_role->dn), this_role->avl_tree);
+
+ return 0;
+}
diff --git a/ldap/servers/plugins/roles/roles_cache.h b/ldap/servers/plugins/roles/roles_cache.h
new file mode 100644
index 00000000..fbe6c641
--- /dev/null
+++ b/ldap/servers/plugins/roles/roles_cache.h
@@ -0,0 +1,52 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#if !defined( _ROLES_CACHE_H )
+
+#define SLAPD_ROLES_INTERFACE "roles-slapd"
+#define ROLES_PLUGIN_SUBSYSTEM "roles-plugin"
+#define NSROLEATTR "nsRole"
+
+#define ROLE_DEFINITION_FILTER "(&(objectclass=nsRoleDefinition)(objectclass=ldapsubentry))"
+#define OBJ_FILTER "(|(objectclass=*)(objectclass=ldapsubentry))"
+
+#define ROLE_TYPE_MANAGED 1
+#define ROLE_TYPE_FILTERED 2
+#define ROLE_TYPE_NESTED 3
+
+#define ROLE_OBJECTCLASS_MANAGED "nsManagedRoleDefinition"
+#define ROLE_OBJECTCLASS_FILTERED "nsFilteredRoleDefinition"
+#define ROLE_OBJECTCLASS_NESTED "nsNestedRoleDefinition"
+
+#define ROLE_FILTER_ATTR_NAME "nsRoleFilter"
+#define ROLE_MANAGED_ATTR_NAME "nsRoleDN"
+#define ROLE_NESTED_ATTR_NAME "nsRoleDN"
+
+#define SLAPI_ROLE_ERROR_NO_FILTER_SPECIFIED -1
+#define SLAPI_ROLE_ERROR_FILTER_BAD -2
+#define SLAPI_ROLE_DEFINITION_DOESNT_EXIST -3
+#define SLAPI_ROLE_DEFINITION_ERROR -4
+#define SLAPI_ROLE_DEFINITION_ALREADY_EXIST -5
+
+/* From roles_cache.c */
+int roles_cache_init();
+void roles_cache_stop();
+void roles_cache_change_notify(Slapi_PBlock *pb);
+int roles_cache_listroles(Slapi_Entry *entry, int return_value, Slapi_ValueSet **valueset_out);
+
+int roles_check(Slapi_Entry *entry_to_check, Slapi_DN *role_dn, int *present);
+
+/* From roles_plugin.c */
+int roles_init( Slapi_PBlock *pb );
+int roles_sp_get_value(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, char *type, Slapi_ValueSet** results,int *type_name_disposition, char** actual_type_name, int flags, int *free_flags, void *hint);
+
+int roles_sp_compare_value(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, char *type, Slapi_Value *test_this, int* result,int flags, void *hint);
+
+int roles_sp_list_types(vattr_sp_handle *handle,Slapi_Entry *e,vattr_type_list_context *type_context,int flags);
+
+void * roles_get_plugin_identity();
+
+#endif /* _ROLES_CACHE_H */
diff --git a/ldap/servers/plugins/roles/roles_plugin.c b/ldap/servers/plugins/roles/roles_plugin.c
new file mode 100644
index 00000000..d1ce5903
--- /dev/null
+++ b/ldap/servers/plugins/roles/roles_plugin.c
@@ -0,0 +1,254 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+/*
+ Code to implement server roles features
+*/
+
+#include "slap.h"
+
+#include "vattr_spi.h"
+
+#include "roles_cache.h"
+#include "statechange.h"
+
+
+#ifdef SOURCEFILE
+#undef SOURCEFILE
+#endif
+#define SOURCEFILE "roles_plugin.c"
+static char *sourcefile = SOURCEFILE;
+
+#define STATECHANGE_ROLES_ID "Roles"
+#define STATECHANGE_ROLES_CONFG_FILTER "objectclass=nsRoleDefinition"
+#define STATECHANGE_ROLES_ENTRY_FILTER "objectclass=*"
+
+#define ROLES_PLUGIN_SUBSYSTEM "roles-plugin" /* for logging */
+static void * roles_plugin_identity = NULL;
+
+static Slapi_PluginDesc pdesc = { "roles",
+ PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "roles plugin" };
+
+#ifdef _WIN32
+int *module_ldap_debug = 0;
+
+void plugin_init_debug_level(int *level_ptr)
+{
+ module_ldap_debug = level_ptr;
+}
+#endif
+
+static int roles_start( Slapi_PBlock *pb );
+static int roles_post_op( Slapi_PBlock *pb );
+static int roles_close( Slapi_PBlock *pb );
+static void roles_set_plugin_identity(void * identity);
+
+/* roles_init
+ ----------
+ Initialization of the plugin
+ */
+int roles_init( Slapi_PBlock *pb )
+{
+ int rc = 0;
+ void *plugin_identity = NULL;
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM,
+ "=> roles_init\n" );
+
+ slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &plugin_identity);
+ PR_ASSERT (plugin_identity);
+ roles_set_plugin_identity(plugin_identity);
+
+ if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION,
+ (void *)SLAPI_PLUGIN_VERSION_01 ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION,
+ (void *)&pdesc ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_START_FN,
+ (void *)roles_start ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODIFY_FN,
+ (void *) roles_post_op ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODRDN_FN,
+ (void *) roles_post_op ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_POST_ADD_FN,
+ (void *) roles_post_op ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_POST_DELETE_FN,
+ (void *) roles_post_op ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN,
+ (void *) roles_close ) != 0 )
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, ROLES_PLUGIN_SUBSYSTEM,
+ "roles_init failed\n" );
+ rc = -1;
+ }
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM,
+ "<= roles_init %d\n", rc );
+ return rc;
+}
+
+/* roles_start
+ -----------
+ kexcoff: cache build at init or at startup ?
+ */
+static int roles_start( Slapi_PBlock *pb )
+{
+ int rc = 0;
+ void **statechange_api;
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM,
+ "=> roles_start\n" );
+
+ roles_cache_init();
+
+ /* from Pete Rowley for vcache
+ * PLUGIN DEPENDENCY ON STATECHANGE PLUGIN
+ *
+ * register objectclasses which indicate a
+ * role configuration entry, and therefore
+ * a globally significant change for the vcache
+ */
+
+ if(!slapi_apib_get_interface(StateChange_v1_0_GUID, &statechange_api))
+ {
+ statechange_register(statechange_api, STATECHANGE_ROLES_ID, NULL, STATECHANGE_ROLES_CONFG_FILTER, &vattr_global_invalidate, (notify_callback) statechange_vattr_cache_invalidator_callback(statechange_api));
+ }
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM,
+ "<= roles_start %d\n", rc );
+ return rc;
+}
+
+/* roles_close
+ -----------
+ kexcoff: ??
+ */
+static int roles_close( Slapi_PBlock *pb )
+{
+ int rc = 0;
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM,
+ "=> roles_close\n" );
+
+ roles_cache_stop();
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM,
+ "<= roles_close %d\n", rc );
+ return rc;
+}
+
+/* roles_sp_get_value
+ ------------------
+ Enumerate the values of the role attribute.
+ We do this by first locating all the roles which are in scope
+ Then we iterate over the in-scope roles calling Slapi_Role_Check().
+ For those which pass the check, we add their DN to the attribute's value set.
+*/
+int roles_sp_get_value(vattr_sp_handle *handle,
+ vattr_context *c,
+ Slapi_Entry *e,
+ char *type,
+ Slapi_ValueSet** results,
+ int *type_name_disposition,
+ char** actual_type_name,
+ int flags,
+ int *free_flags,
+ void *hint)
+{
+ int rc = -1;
+
+ rc = roles_cache_listroles(e, 1, results);
+ if (rc == 0)
+ {
+ *free_flags = SLAPI_VIRTUALATTRS_RETURNED_COPIES;
+ *actual_type_name = strdup(NSROLEATTR);
+
+ if (type_name_disposition)
+ {
+ *type_name_disposition = SLAPI_VIRTUALATTRS_TYPE_NAME_MATCHED_EXACTLY_OR_ALIAS;
+ }
+ }
+
+ /* Need to check the return code here because the caller
+ doesn't understand roles return codes */
+
+ return rc;
+}
+
+
+/* roles_sp_compare_value
+ ----------------------
+ Compare the value of the role attribute with a presented value.
+ Return true or false to the client.
+ */
+
+int roles_sp_compare_value(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, char *type, Slapi_Value *test_this, int* result,int flags, void *hint)
+{
+ int rc = 0;
+ Slapi_DN the_dn;
+
+ /* Extract the role's DN from the value passed in */
+ slapi_sdn_init_dn_byref(&the_dn,slapi_value_get_string(test_this));
+
+ return (roles_check(e,&the_dn,result));
+}
+
+int roles_sp_list_types(vattr_sp_handle *handle,Slapi_Entry *e,vattr_type_list_context *type_context,int flags)
+{
+ static char* test_type_name = NSROLEATTR;
+ int ret =0;
+
+ if ( 0 == ( flags & SLAPI_VIRTUALATTRS_LIST_OPERATIONAL_ATTRS )) {
+ /*
+ * Operational attributes were NOT requested. Since the only
+ * attribute type we service is nsRole which IS operational,
+ * there is nothing for us to do in this case.
+ */
+ return 0;
+ }
+
+ ret = roles_cache_listroles(e, 0, NULL);
+ if(ret == 0)
+ {
+ vattr_type_thang thang = {0};
+ thang.type_name = test_type_name;
+ thang.type_flags = SLAPI_ATTR_FLAG_OPATTR;
+ slapi_vattrspi_add_type(type_context,&thang,SLAPI_VIRTUALATTRS_REQUEST_POINTERS);
+ }
+ return 0;
+}
+
+/* What do we do on shutdown ? */
+int roles_sp_cleanup()
+{
+ return 0;
+}
+
+/* roles_post_op
+ -----------
+ Catch all for all post operations that change entries
+ in some way - this simply notifies the cache of a
+ change - the cache decides if action is necessary
+*/
+static int roles_post_op( Slapi_PBlock *pb )
+{
+ slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "--> roles_post_op\n");
+
+ roles_cache_change_notify(pb);
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "<-- roles_post_op\n");
+ return 0; /* always succeed */
+}
+
+static void roles_set_plugin_identity(void * identity)
+{
+ roles_plugin_identity=identity;
+}
+
+void * roles_get_plugin_identity()
+{
+ return roles_plugin_identity;
+}
+
diff --git a/ldap/servers/plugins/shared/Makefile b/ldap/servers/plugins/shared/Makefile
new file mode 100644
index 00000000..847735ee
--- /dev/null
+++ b/ldap/servers/plugins/shared/Makefile
@@ -0,0 +1,56 @@
+#
+# BEGIN COPYRIGHT BLOCK
+# Copyright 2001 Sun Microsystems, Inc.
+# Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+# All rights reserved.
+# END COPYRIGHT BLOCK
+#
+#
+# GNU Makefile for shared components for Directory Server plugins
+#
+#
+
+LDAP_SRC = ../../..
+MCOM_ROOT = ../../../../..
+
+SHARED=shared
+
+NOSTDCLEAN=true # don't let nsconfig.mk define target clean
+NOSTDSTRIP=true # don't let nsconfig.mk define target strip
+#NSPR20=true # probably should be defined somewhere else (not sure where)
+
+OBJDEST = $(OBJDIR)/lib/$(SHARED)
+LIBDIR = $(LDAP_LIBDIR)
+
+include $(MCOM_ROOT)/ldapserver/nsdefs.mk
+include $(MCOM_ROOT)/ldapserver/nsconfig.mk
+include $(LDAP_SRC)/nsldap.mk
+
+CFLAGS+=$(SLCFLAGS)
+
+INCLUDES += -I$(LDAP_SRC)/servers/slapd
+
+LOCAL_OBJS= utils.o
+
+OBJS = $(addprefix $(OBJDEST)/, $(LOCAL_OBJS))
+
+ifeq ($(ARCH), WINNT)
+EXTRA_LIBS_DEP += $(LIBSLAPD_DEP)
+EXTRA_LIBS += $(LIBSLAPD)
+endif
+
+all: $(OBJDEST) $(OBJS)
+
+veryclean: clean
+
+clean:
+ $(RM) $(OBJS)
+
+$(OBJDEST):
+ $(MKDIR) $(OBJDEST)
+
+#
+# header file dependencies (incomplete)
+#
+$(OBJS): plugin-utils.h
+
diff --git a/ldap/servers/plugins/shared/plugin-utils.h b/ldap/servers/plugins/shared/plugin-utils.h
new file mode 100644
index 00000000..31c956f4
--- /dev/null
+++ b/ldap/servers/plugins/shared/plugin-utils.h
@@ -0,0 +1,77 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/***********************************************************************
+**
+** NAME
+** plugin-utils.h
+**
+** DESCRIPTION
+**
+**
+** AUTHOR
+** <rweltman@netscape.com>
+**
+***********************************************************************/
+
+#ifndef _PLUGIN_UTILS_H_
+#define _PLUGIN_UTILS_H_
+
+/***********************************************************************
+** Includes
+***********************************************************************/
+
+#include <slapi-plugin.h>
+/*
+ * slapi-plugin-compat4.h is needed because we use the following deprecated
+ * functions:
+ *
+ * slapi_search_internal()
+ * slapi_modify_internal()
+ */
+#include "slapi-plugin-compat4.h"
+#include <dirlite_strings.h>
+#include <stdio.h>
+#include <string.h>
+#ifdef _WINDOWS
+#undef strcasecmp
+#define strcasecmp strcmpi
+#endif
+#include "dirver.h"
+
+#ifdef LDAP_DEBUG
+#ifndef DEBUG
+#define DEBUG
+#endif
+#endif
+
+#define BEGIN do {
+#define END } while(0);
+
+int initCounterLock();
+int op_error(int internal_error);
+Slapi_PBlock *readPblockAndEntry( const char *baseDN, const char *filter,
+ char *attrs[] );
+int entryHasObjectClass(Slapi_PBlock *pb, Slapi_Entry *e,
+ const char *objectClass);
+Slapi_PBlock *dnHasObjectClass( const char *baseDN, const char *objectClass );
+Slapi_PBlock *dnHasAttribute( const char *baseDN, const char *attrName );
+int setCounter( Slapi_Entry *e, const char *attrName, int value );
+int updateCounter( Slapi_Entry *e, const char *attrName, int increment );
+int updateCounterByDN( const char *dn, const char *attrName, int increment );
+
+typedef struct DNLink {
+ char *dn;
+ void *data;
+ struct DNLink *next;
+} DNLink;
+
+DNLink *cacheInit( void );
+DNLink *cacheAdd( DNLink *root, char *dn, void *data );
+char *cacheRemove( DNLink *root, char *dn );
+int cacheDelete( DNLink *root, char *dn );
+DNLink *cacheFind( DNLink *root, char *dn );
+
+#endif /* _PLUGIN_UTILS_H_ */
diff --git a/ldap/servers/plugins/shared/utils.c b/ldap/servers/plugins/shared/utils.c
new file mode 100644
index 00000000..d5044b17
--- /dev/null
+++ b/ldap/servers/plugins/shared/utils.c
@@ -0,0 +1,467 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/***********************************************************************
+** NAME
+** utils.c
+**
+** DESCRIPTION
+**
+**
+** AUTHOR
+** <rweltman@netscape.com>
+**
+***********************************************************************/
+
+
+/***********************************************************************
+** Includes
+***********************************************************************/
+
+#include "plugin-utils.h"
+
+static char *plugin_name = "utils";
+
+/*
+ * Lock for updating a counter (global for all counters)
+ */
+static Slapi_Mutex *counter_lock = NULL;
+
+/* ------------------------------------------------------------ */
+/*
+ * op_error - Record (and report) an operational error.
+ */
+int
+op_error(int internal_error) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
+ "Internal error: %d\n", internal_error);
+
+ return LDAP_OPERATIONS_ERROR;
+}
+
+int initCounterLock() {
+ if ( NULL == counter_lock ) {
+ if ( !(counter_lock = slapi_new_mutex()) ) {
+ return 200;
+ }
+ }
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/*
+ * readPblockAndEntry - search for and read an entry
+ * Return:
+ * A pblock containing the entry, or NULL
+ */
+Slapi_PBlock *
+readPblockAndEntry( const char *baseDN, const char *filter,
+ char *attrs[] ) {
+ int result = 0;
+ Slapi_PBlock *spb = NULL;
+
+ BEGIN
+ int sres;
+
+ /* Perform the search - the new pblock needs to be freed */
+ spb = slapi_search_internal((char *)baseDN, LDAP_SCOPE_BASE,
+ (char *)filter, NULL, attrs, 0);
+ if ( !spb ) {
+ result = op_error(20);
+ break;
+ }
+
+ if ( slapi_pblock_get( spb, SLAPI_PLUGIN_INTOP_RESULT, &sres ) ) {
+ result = op_error(21);
+ break;
+ } else if (sres) {
+ result = op_error(22);
+ break;
+ }
+ END
+
+ return spb;
+}
+
+/* ------------------------------------------------------------ */
+/*
+ * hasObjectClass - read an entry and check if it has a
+ * particular object class value
+ * Return:
+ * 1 - the entry contains the object class value
+ * 0 - the entry doesn't contain the object class value
+ */
+int
+entryHasObjectClass(Slapi_PBlock *pb, Slapi_Entry *e,
+ const char *objectClass) {
+ Slapi_Attr *attr;
+ Slapi_Value *v;
+ const struct berval *bv;
+ int vhint;
+
+ if ( slapi_entry_attr_find(e, "objectclass", &attr) ) {
+ return 0; /* no objectclass values! */
+ }
+
+ /*
+ * Check each of the object class values in turn.
+ */
+ for ( vhint = slapi_attr_first_value( attr, &v );
+ vhint != -1;
+ vhint = slapi_attr_next_value( attr, vhint, &v )) {
+ bv = slapi_value_get_berval(v);
+ if ( NULL != bv && NULL != bv->bv_val &&
+ !strcasecmp(bv->bv_val, objectClass) ) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/*
+ * dnHasObjectClass - read an entry if it has a particular object class
+ * Return:
+ * A pblock containing the entry, or NULL
+ */
+Slapi_PBlock *
+dnHasObjectClass( const char *baseDN, const char *objectClass ) {
+ int result = 0;
+ Slapi_PBlock *spb = NULL;
+
+ BEGIN
+ Slapi_Entry **entries;
+ char filter[1024];
+ char *attrs[2];
+
+ /* Perform the search - the new pblock needs to be freed */
+ attrs[0] = "objectclass";
+ attrs[1] = NULL;
+ sprintf( filter, "objectclass=%s", objectClass );
+ if ( !(spb = readPblockAndEntry( baseDN, filter, attrs) ) ) {
+ break;
+ }
+
+ if ( slapi_pblock_get(spb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES,
+ &entries) ) {
+ result = op_error(23);
+ break;
+ }
+ /*
+ * Can only be one entry returned on a base search; just check
+ * the first one
+ */
+ if ( !*entries ) {
+ /* Clean up */
+ slapi_free_search_results_internal(spb);
+ slapi_pblock_destroy(spb);
+ spb = NULL;
+ }
+ END
+
+ return spb;
+}
+
+/* ------------------------------------------------------------ */
+/*
+ * dnHasAttribute - read an entry if it has a particular attribute
+ * Return:
+ * The entry, or NULL
+ */
+Slapi_PBlock *
+dnHasAttribute( const char *baseDN, const char *attrName ) {
+ int result = 0;
+ Slapi_PBlock *spb = NULL;
+
+ BEGIN
+ int sres;
+ Slapi_Entry **entries;
+ char filter[1024];
+ char *attrs[2];
+
+ /* Perform the search - the new pblock needs to be freed */
+ attrs[0] = (char *)attrName;
+ attrs[1] = NULL;
+ sprintf( filter, "%s=*", attrName );
+ spb = slapi_search_internal((char *)baseDN, LDAP_SCOPE_BASE,
+ filter, NULL, attrs, 0);
+ if ( !spb ) {
+ result = op_error(20);
+ break;
+ }
+
+ if ( slapi_pblock_get( spb, SLAPI_PLUGIN_INTOP_RESULT, &sres ) ) {
+ result = op_error(21);
+ break;
+ } else if (sres) {
+ result = op_error(22);
+ break;
+ }
+
+ if ( slapi_pblock_get(spb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES,
+ &entries) ) {
+ result = op_error(23);
+ break;
+ }
+ /*
+ * Can only be one entry returned on a base search; just check
+ * the first one
+ */
+ if ( !*entries ) {
+ /* Clean up */
+ slapi_free_search_results_internal(spb);
+ slapi_pblock_destroy(spb);
+ spb = NULL;
+ }
+ END
+
+ return spb;
+}
+
+/* ------------------------------------------------------------ */
+/*
+ * setCounter - set the value of a counter
+ *
+ * Return:
+ * LDAP_SUCCESS - updated the attribute
+ * other - failure to update the count
+ */
+int
+setCounter( Slapi_Entry *e, const char *attrName, int value ) {
+ int result = LDAP_SUCCESS;
+ Slapi_PBlock *modifySpb = NULL;
+ char strValue[16];
+ char *strValues[2] = { NULL };
+ LDAPMod mod;
+ LDAPMod *mods[2];
+ int res;
+
+ BEGIN
+ /* Store the updated value */
+ strValues[0] = strValue;
+ sprintf( strValue, "%d", value );
+ mod.mod_op = LDAP_MOD_REPLACE;
+ mod.mod_type = (char *)attrName;
+ mod.mod_values = strValues;
+ mods[0] = &mod;
+ mods[1] = NULL;
+ modifySpb = slapi_modify_internal( slapi_entry_get_dn(e), mods,
+ NULL, 1 );
+ /* Check if the operation succeeded */
+ if ( slapi_pblock_get( modifySpb, SLAPI_PLUGIN_INTOP_RESULT,
+ &res ) ) {
+ result = op_error(33);
+ break;
+ } else if (res) {
+ result = op_error(34);
+ break;
+ }
+ slapi_pblock_destroy(modifySpb);
+ END
+ return result;
+}
+
+/* ------------------------------------------------------------ */
+/*
+ * updateCounter - read and increment/decrement the value of a counter
+ *
+ * Return:
+ * LDAP_SUCCESS - updated the attribute
+ * other - failure to update the count
+ */
+int
+updateCounter( Slapi_Entry *e, const char *attrName, int increment ) {
+ int result = LDAP_SUCCESS;
+ Slapi_PBlock *modifySpb = NULL;
+ Slapi_Attr *attr;
+ int value = 0;
+ char strValue[16];
+ char *strValues[2] = { NULL };
+ LDAPMod mod;
+ LDAPMod *mods[2];
+ int res;
+
+ BEGIN
+ /* Lock the entry */
+ slapi_lock_mutex(counter_lock);
+ /* Get the count attribute */
+ if ( slapi_entry_attr_find(e, (char *)attrName, &attr) ) {
+ /* No count yet; that's OK */
+ } else {
+ /* Get the first value for the attribute */
+ Slapi_Value *v = NULL;
+ const struct berval *bv = NULL;
+
+ if ( -1 == slapi_attr_first_value( attr, &v ) || NULL == v ||
+ NULL == ( bv = slapi_value_get_berval(v)) ||
+ NULL == bv->bv_val ) {
+ /* No values yet; that's OK, too */
+ } else {
+ value = atoi( bv->bv_val );
+ }
+ }
+ /* Add the increment */
+ value += increment;
+ if ( value < 0 ) {
+ value = 0;
+ }
+ /* Store the updated value */
+ strValues[0] = strValue;
+ sprintf( strValue, "%d", value );
+ mod.mod_op = LDAP_MOD_REPLACE;
+ mod.mod_type = (char *)attrName;
+ mod.mod_values = strValues;
+ mods[0] = &mod;
+ mods[1] = NULL;
+ modifySpb = slapi_modify_internal( slapi_entry_get_dn(e), mods,
+ NULL, 1 );
+ /* Check if the operation succeeded */
+ if ( slapi_pblock_get( modifySpb, SLAPI_PLUGIN_INTOP_RESULT,
+ &res ) ) {
+ result = op_error(33);
+ break;
+ } else if (res) {
+ result = op_error(34);
+ break;
+ }
+ slapi_pblock_destroy(modifySpb);
+ /* Unlock the entry */
+ slapi_unlock_mutex(counter_lock);
+#ifdef DEBUG
+ slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
+ "adjusted %s in %s by %d to %d\n",
+ attrName, slapi_entry_get_dn(e), increment, value);
+#endif
+
+ END
+ return result;
+}
+
+/* ------------------------------------------------------------ */
+/*
+ * updateCounterByDN - read and increment/decrement the value of a counter
+ *
+ * Return:
+ * LDAP_SUCCESS - updated the attribute
+ * other - failure to update the count
+ */
+int
+updateCounterByDN( const char *dn, const char *attrName, int increment ) {
+ int result = LDAP_SUCCESS;
+ Slapi_PBlock *spb = NULL;
+ Slapi_Entry **entries;
+
+ BEGIN
+ char *attrs[2];
+ int sres;
+
+ /* Perform the search - the new pblock needs to be freed */
+ attrs[0] = (char *)attrName;
+ attrs[1] = NULL;
+ spb = slapi_search_internal((char *)dn, LDAP_SCOPE_BASE,
+ "objectclass=*", NULL, attrs, 0);
+ if ( !spb ) {
+ result = op_error(20);
+ break;
+ }
+
+ if ( slapi_pblock_get( spb, SLAPI_PLUGIN_INTOP_RESULT, &sres ) ) {
+ result = op_error(21);
+ break;
+ } else if (sres) {
+ result = op_error(22);
+ break;
+ }
+
+ if ( slapi_pblock_get(spb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES,
+ &entries) ) {
+ result = op_error(23);
+ break;
+ }
+ END
+ if ( 0 == result ) {
+ result = updateCounter( *entries, attrName, increment );
+ }
+ if ( NULL != spb ) {
+ /* Clean up */
+ slapi_free_search_results_internal(spb);
+ slapi_pblock_destroy(spb);
+ }
+ return result;
+}
+
+/*
+ * Lock for accessing a cache (global for all caches)
+ */
+static Slapi_Mutex *cache_lock = NULL;
+
+DNLink *cacheInit() {
+ DNLink *root;
+ slapi_lock_mutex(cache_lock);
+ root = (DNLink *)malloc( sizeof(DNLink) );
+ root->next = NULL;
+ root->data = NULL;
+ root->dn = (char *)malloc(1);
+ root->dn[0] = 0;
+ slapi_unlock_mutex(cache_lock);
+ return root;
+}
+
+DNLink *cacheAdd( DNLink *root, char *dn, void *data ) {
+ if ( NULL == root ) {
+ return NULL;
+ }
+ slapi_lock_mutex(cache_lock);
+ for( ; root->next; root = root->next ) {
+ }
+ root->next = (DNLink *)malloc( sizeof(DNLink) );
+ root = root->next;
+ root->dn = dn;
+ root->data = data;
+ root->next = NULL;
+ slapi_unlock_mutex(cache_lock);
+ return root;
+}
+
+char *cacheRemove( DNLink *root, char *dn ) {
+ char *found = NULL;
+ DNLink *current = root;
+ DNLink *prev = NULL;
+ if ( NULL == root ) {
+ return NULL;
+ }
+ slapi_lock_mutex(cache_lock);
+ for( ; current; prev = current, current = current->next ) {
+ if ( !strcmp( current->dn, dn ) ) {
+ found = current->dn;
+ prev->next = current->next;
+ slapi_ch_free( (void **)&current );
+ break;
+ }
+ }
+ slapi_unlock_mutex(cache_lock);
+ return found;
+}
+
+int cacheDelete( DNLink *root, char *dn ) {
+ char *found = cacheRemove( root, dn );
+ if ( found ) {
+ slapi_ch_free( (void **)&found );
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+DNLink *cacheFind( DNLink *root, char *dn ) {
+ if ( NULL == root ) {
+ return NULL;
+ }
+ slapi_lock_mutex(cache_lock);
+ for( ; root && strcmp(dn, root->dn); root = root->next ) {
+ }
+ slapi_unlock_mutex(cache_lock);
+ return root;
+}
diff --git a/ldap/servers/plugins/statechange/Makefile b/ldap/servers/plugins/statechange/Makefile
new file mode 100644
index 00000000..bc04a3f0
--- /dev/null
+++ b/ldap/servers/plugins/statechange/Makefile
@@ -0,0 +1,78 @@
+#
+# BEGIN COPYRIGHT BLOCK
+# Copyright 2001 Sun Microsystems, Inc.
+# Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+# All rights reserved.
+# END COPYRIGHT BLOCK
+#
+LDAP_SRC = ../../..
+MCOM_ROOT = ../../../../..
+
+NOSTDCLEAN=true # don't let nsconfig.mk define target clean
+NOSTDSTRIP=true # don't let nsconfig.mk define target strip
+NSPR20=true # probably should be defined somewhere else (not sure where)
+
+OBJDEST = $(OBJDIR)/lib/libstatechange
+LIBDIR = $(LIB_RELDIR)
+
+include $(MCOM_ROOT)/ldapserver/nsdefs.mk
+include $(MCOM_ROOT)/ldapserver/nsconfig.mk
+include $(LDAP_SRC)/nsldap.mk
+
+ifeq ($(ARCH), WINNT)
+DEF_FILE:=./statechange.def
+endif
+
+STATECHANGE_OBJS = statechange.o
+OBJS = $(addprefix $(OBJDEST)/, $(STATECHANGE_OBJS))
+
+STATECHANGE_DLL = statechange-plugin
+
+INCLUDES += -I../../slapd -I../../../include
+CFLAGS+=$(SLCFLAGS) -DSLAPD_LOGGING
+
+ifeq ($(ARCH), WINNT)
+EXTRA_LIBS_DEP += $(LIBSLAPD)
+EXTRA_LIBS += $(NSPRLINK) $(LIBSLAPD)
+STATECHANGE_DLL_OBJ = $(addprefix $(OBJDEST)/, dllmain.o)
+endif
+
+ifeq ($(ARCH), HPUX)
+EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAPSDK_DEP) $(NSPR_DEP) $(SECURITY_DEP)
+EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(SECURITYLINK) $(NSPRLINK) $(ICULINK)
+endif
+
+ifeq ($(ARCH), AIX)
+LD=ld
+EXTRA_LIBS += $(LIBSLAPD)
+endif
+
+STATECHANGE= $(addprefix $(LIBDIR)/, $(STATECHANGE_DLL).$(DLL_SUFFIX))
+
+clientSDK:
+
+all: $(OBJDEST) $(LIBDIR) $(STATECHANGE)
+
+ifeq ($(ARCH), WINNT)
+$(STATECHANGE): $(OBJS) $(STATECHANGE_DLL_OBJ) $(DEF_FILE)
+ $(LINK_DLL) $(STATECHANGE_DLL_OBJ) $(EXTRA_LIBS) /DEF:$(DEF_FILE)
+else
+$(STATECHANGE): $(OBJS) $(STATECHANGE_DLL_OBJ)
+ $(LINK_DLL) $(STATECHANGE_DLL_OBJ) $(EXTRA_LIBS)
+endif
+
+
+veryclean: clean
+
+clean:
+ $(RM) $(OBJS)
+ifeq ($(ARCH), WINNT)
+ $(RM) $(STATECHANGE_DLL_OBJ)
+endif
+ $(RM) $(STATECHANGE)
+
+$(OBJDEST):
+ $(MKDIR) $(OBJDEST)
+
+$(LIBDIR):
+ $(MKDIR) $(LIBDIR)
diff --git a/ldap/servers/plugins/statechange/dllmain.c b/ldap/servers/plugins/statechange/dllmain.c
new file mode 100644
index 00000000..fabf8677
--- /dev/null
+++ b/ldap/servers/plugins/statechange/dllmain.c
@@ -0,0 +1,96 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/*
+ * Microsoft Windows specifics for BACK-LDBM DLL
+ */
+#include "ldap.h"
+#include "lber.h"
+
+
+#ifdef _WIN32
+/* Lifted from Q125688
+ * How to Port a 16-bit DLL to a Win32 DLL
+ * on the MSVC 4.0 CD
+ */
+BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved)
+{
+ WSADATA wsadata;
+
+ switch (fdwReason)
+ {
+ case DLL_PROCESS_ATTACH:
+ /* Code from LibMain inserted here. Return TRUE to keep the
+ DLL loaded or return FALSE to fail loading the DLL.
+
+ You may have to modify the code in your original LibMain to
+ account for the fact that it may be called more than once.
+ You will get one DLL_PROCESS_ATTACH for each process that
+ loads the DLL. This is different from LibMain which gets
+ called only once when the DLL is loaded. The only time this
+ is critical is when you are using shared data sections.
+ If you are using shared data sections for statically
+ allocated data, you will need to be careful to initialize it
+ only once. Check your code carefully.
+
+ Certain one-time initializations may now need to be done for
+ each process that attaches. You may also not need code from
+ your original LibMain because the operating system may now
+ be doing it for you.
+ */
+ /*
+ * 16 bit code calls UnlockData()
+ * which is mapped to UnlockSegment in windows.h
+ * in 32 bit world UnlockData is not defined anywhere
+ * UnlockSegment is mapped to GlobalUnfix in winbase.h
+ * and the docs for both UnlockSegment and GlobalUnfix say
+ * ".. function is oboslete. Segments have no meaning
+ * in the 32-bit environment". So we do nothing here.
+ */
+
+ if( errno = WSAStartup(0x0101, &wsadata ) != 0 )
+ return FALSE;
+
+ break;
+
+ case DLL_THREAD_ATTACH:
+ /* Called each time a thread is created in a process that has
+ already loaded (attached to) this DLL. Does not get called
+ for each thread that exists in the process before it loaded
+ the DLL.
+
+ Do thread-specific initialization here.
+ */
+ break;
+
+ case DLL_THREAD_DETACH:
+ /* Same as above, but called when a thread in the process
+ exits.
+
+ Do thread-specific cleanup here.
+ */
+ break;
+
+ case DLL_PROCESS_DETACH:
+ /* Code from _WEP inserted here. This code may (like the
+ LibMain) not be necessary. Check to make certain that the
+ operating system is not doing it for you.
+ */
+ WSACleanup();
+
+ break;
+ }
+ /* The return value is only used for DLL_PROCESS_ATTACH; all other
+ conditions are ignored. */
+ return TRUE; // successful DLL_PROCESS_ATTACH
+}
+#else
+int CALLBACK
+LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine )
+{
+ /*UnlockData( 0 );*/
+ return( 1 );
+}
+#endif
diff --git a/ldap/servers/plugins/statechange/statechange.c b/ldap/servers/plugins/statechange/statechange.c
new file mode 100644
index 00000000..c0942b41
--- /dev/null
+++ b/ldap/servers/plugins/statechange/statechange.c
@@ -0,0 +1,450 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* plugin which provides a callback mechanism for state changes in the DS */
+
+#include <stdio.h>
+#include <string.h>
+#include "portable.h"
+#include "slapi-plugin.h"
+#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */
+#include "dirver.h"
+#include "statechange.h"
+
+/* get file mode flags for unix */
+#ifndef _WIN32
+#include <sys/stat.h>
+#endif
+
+/* the circular list of systems to notify */
+typedef struct _statechange_notify
+{
+ char *caller_id;
+ char *dn;
+ char *filter;
+ Slapi_Filter *realfilter;
+ notify_callback func;
+ void *caller_data;
+ struct _statechange_notify *next;
+ struct _statechange_notify *prev;
+} SCNotify;
+
+static SCNotify *head; /* a place to start in the list */
+
+#define SCN_PLUGIN_SUBSYSTEM "statechange-plugin" /* used for logging */
+
+static void *api[5];
+static Slapi_Mutex *buffer_lock = 0;
+
+/* other function prototypes */
+int statechange_init( Slapi_PBlock *pb );
+static int statechange_start( Slapi_PBlock *pb );
+static int statechange_close( Slapi_PBlock *pb );
+static int statechange_post_op( Slapi_PBlock *pb, int modtype );
+static int statechange_mod_post_op( Slapi_PBlock *pb );
+static int statechange_modrdn_post_op( Slapi_PBlock *pb );
+static int statechange_add_post_op( Slapi_PBlock *pb );
+static int statechange_delete_post_op( Slapi_PBlock *pb );
+static int _statechange_register(char *caller_id, char *dn, char *filter, void *caller_data, notify_callback func);
+static void *_statechange_unregister(char *dn, char *filter, notify_callback func);
+static void _statechange_unregister_all(char *caller_id, caller_data_free_callback);
+static void _statechange_vattr_cache_invalidator_callback(Slapi_Entry *e, char *dn, int modtype, Slapi_PBlock *pb, void *caller_data);
+static SCNotify *statechange_find_notify(char *dn, char *filter, notify_callback func);
+
+
+static Slapi_PluginDesc pdesc = { "statechange", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT,
+ "state change notification service plugin" };
+
+
+#ifdef _WIN32
+int *module_ldap_debug = 0;
+
+void plugin_init_debug_level(int *level_ptr)
+{
+ module_ldap_debug = level_ptr;
+}
+#endif
+
+
+/*
+ statechange_init
+ --------
+ adds our callbacks to the list
+*/
+int statechange_init( Slapi_PBlock *pb )
+{
+ int ret = 0;
+
+ slapi_log_error( SLAPI_LOG_TRACE, SCN_PLUGIN_SUBSYSTEM, "--> statechange_init\n");
+
+ head = 0;
+
+ if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION,
+ SLAPI_PLUGIN_VERSION_01 ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN,
+ (void *) statechange_start ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODIFY_FN,
+ (void *) statechange_mod_post_op ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODRDN_FN,
+ (void *) statechange_modrdn_post_op ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_POST_ADD_FN,
+ (void *) statechange_add_post_op ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_POST_DELETE_FN,
+ (void *) statechange_delete_post_op ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN,
+ (void *) statechange_close ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION,
+ (void *)&pdesc ) != 0 )
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, SCN_PLUGIN_SUBSYSTEM,
+ "statechange_init: failed to register plugin\n" );
+ ret = -1;
+ }
+
+ slapi_log_error( SLAPI_LOG_TRACE, SCN_PLUGIN_SUBSYSTEM, "<-- statechange_init\n");
+ return ret;
+}
+
+
+/*
+ statechange_start
+ ---------
+ This function publishes the interface for this plugin
+*/
+static int statechange_start( Slapi_PBlock *pb )
+{
+ int ret = 0;
+
+ slapi_log_error( SLAPI_LOG_TRACE, SCN_PLUGIN_SUBSYSTEM, "--> statechange_start\n");
+
+ api[0] = 0; /* reserved for api broker use, must be zero */
+ api[1] = (void *)_statechange_register;
+ api[2] = (void *)_statechange_unregister;
+ api[3] = (void *)_statechange_unregister_all;
+ api[4] = (void *)_statechange_vattr_cache_invalidator_callback;
+
+ if(0 == (buffer_lock = slapi_new_mutex())) /* we never free this mutex */
+ {
+ /* badness */
+ slapi_log_error( SLAPI_LOG_FATAL, SCN_PLUGIN_SUBSYSTEM, "statechange: failed to create lock\n");
+ ret = -1;
+ }
+ else
+ {
+ if( slapi_apib_register(StateChange_v1_0_GUID, api) )
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, SCN_PLUGIN_SUBSYSTEM, "statechange: failed to publish state change interface\n");
+ ret = -1;
+ }
+ }
+
+ head = 0;
+
+ slapi_log_error( SLAPI_LOG_TRACE, SCN_PLUGIN_SUBSYSTEM, "<-- statechange_start\n");
+ return ret;
+}
+
+/*
+ statechange_close
+ ---------
+ unregisters the interface for this plugin
+*/
+static int statechange_close( Slapi_PBlock *pb )
+{
+ slapi_log_error( SLAPI_LOG_TRACE, SCN_PLUGIN_SUBSYSTEM, "--> statechange_close\n");
+
+ slapi_apib_unregister(StateChange_v1_0_GUID);
+
+ slapi_log_error( SLAPI_LOG_TRACE, SCN_PLUGIN_SUBSYSTEM, "<-- statechange_close\n");
+
+ return 0;
+}
+
+
+static int statechange_mod_post_op( Slapi_PBlock *pb )
+{
+ return statechange_post_op(pb, LDAP_CHANGETYPE_MODIFY);
+}
+
+static int statechange_modrdn_post_op( Slapi_PBlock *pb )
+{
+ return statechange_post_op(pb, LDAP_CHANGETYPE_MODDN);
+}
+
+static int statechange_add_post_op( Slapi_PBlock *pb )
+{
+ return statechange_post_op(pb, LDAP_CHANGETYPE_ADD);
+}
+
+static int statechange_delete_post_op( Slapi_PBlock *pb )
+{
+ return statechange_post_op(pb, LDAP_CHANGETYPE_DELETE);
+}
+
+
+/*
+ statechange_post_op
+ -----------
+ Catch all for all post operations that change entries
+ in some way - evaluate the change against the notification
+ entries and fire off the relevant callbacks - it is called
+ from the real postop functions which supply it with the
+ postop type
+*/
+static int statechange_post_op( Slapi_PBlock *pb, int modtype )
+{
+ SCNotify *notify = head;
+ int execute;
+ char *dn = NULL;
+ struct slapi_entry *e_before = NULL;
+ struct slapi_entry *e_after = NULL;
+
+ if(head == 0)
+ return 0;
+
+ slapi_log_error( SLAPI_LOG_TRACE, SCN_PLUGIN_SUBSYSTEM, "--> statechange_post_op\n");
+
+ /* evaluate this operation against the notification entries */
+
+ slapi_lock_mutex(buffer_lock);
+ if(head)
+ {
+ if(slapi_pblock_get( pb, SLAPI_TARGET_DN, &dn ))
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, SCN_PLUGIN_SUBSYSTEM, "statechange_post_op: failed to get dn of changed entry");
+ goto bail;
+ }
+
+ slapi_dn_normalize( dn );
+
+ slapi_pblock_get( pb, SLAPI_ENTRY_PRE_OP, &e_before );
+ slapi_pblock_get( pb, SLAPI_ENTRY_POST_OP, &e_after );
+
+ do
+ {
+ execute = 0;
+
+ /* first dn */
+ if(notify && notify->dn)
+ {
+ if(0 != slapi_dn_issuffix(dn, notify->dn))
+ execute = 1;
+ }
+ else
+ /* note, if supplied null for everything in the entry *all* ops match */
+ if(notify)
+ execute = 1;
+
+ if(execute && notify->filter)
+ {
+ /* next the filter */
+ int filter_test = 0;
+
+ /* need to test entry both before and after op */
+ if(e_before && !slapi_filter_test_simple( e_before, notify->realfilter))
+ filter_test = 1;
+
+ if(!filter_test && e_after && !slapi_filter_test_simple( e_after, notify->realfilter))
+ filter_test = 1;
+
+ if(!filter_test)
+ execute = 0;
+ }
+
+ if(execute)
+ {
+ if(e_after)
+ (notify->func)(e_after, dn, modtype, pb, notify->caller_data);
+ else
+ (notify->func)(e_before, dn, modtype, pb, notify->caller_data);
+ }
+
+ notify = notify->next;
+ }
+ while(notify != head);
+ }
+bail:
+ slapi_unlock_mutex(buffer_lock);
+
+ slapi_log_error( SLAPI_LOG_TRACE, SCN_PLUGIN_SUBSYSTEM, "<-- statechange_post_op\n");
+ return 0; /* always succeed */
+}
+
+
+static int _statechange_register(char *caller_id, char *dn, char *filter, void *caller_data, notify_callback func)
+{
+ int ret = -1;
+ SCNotify *item;
+
+ /* simple - we don't check for duplicates */
+
+ item = (SCNotify*)slapi_ch_malloc(sizeof(SCNotify));
+ if(item)
+ {
+ char *writable_filter = slapi_ch_strdup(filter);
+ item->caller_id = slapi_ch_strdup(caller_id);
+ if(dn)
+ {
+ item->dn = slapi_ch_strdup(dn);
+ slapi_dn_normalize( item->dn );
+ }
+ else
+ item->dn = 0;
+ item->filter = slapi_ch_strdup(filter);
+ item->caller_data = caller_data;
+ item->realfilter = slapi_str2filter(writable_filter);
+ item->func = func;
+
+ slapi_lock_mutex(buffer_lock);
+ if(head == NULL)
+ {
+ head = item;
+ head->next = head;
+ head->prev = head;
+ }
+ else
+ {
+ item->next = head;
+ item->prev = head->prev;
+ head->prev = item;
+ item->prev->next = item;
+ }
+ slapi_unlock_mutex(buffer_lock);
+ slapi_ch_free_string(&writable_filter);
+
+ ret = 0;
+ }
+
+ return ret;
+}
+
+static void *_statechange_unregister(char *dn, char *filter, notify_callback thefunc)
+{
+ void *ret = NULL;
+ SCNotify *func = NULL;
+
+ if(buffer_lock == 0)
+ return ret;
+
+ slapi_lock_mutex(buffer_lock);
+
+ if(func = statechange_find_notify(dn, filter, thefunc))
+ {
+ func->prev->next = func->next;
+ func->next->prev = func->prev;
+
+ if(func == head)
+ {
+ head = func->next;
+ }
+
+ if(func == head) /* must be the last item, turn off the lights */
+ head = 0;
+
+ slapi_ch_free_string(&func->caller_id);
+ slapi_ch_free_string(&func->dn);
+ slapi_ch_free_string(&func->filter);
+ slapi_filter_free( func->realfilter, 1 );
+ ret = func->caller_data;
+ slapi_ch_free((void **)&func);
+ }
+
+ slapi_unlock_mutex(buffer_lock);
+
+ return ret;
+}
+
+static void _statechange_unregister_all(char *caller_id, caller_data_free_callback callback)
+{
+ SCNotify *notify = head;
+ SCNotify *start_notify = head;
+
+ if(buffer_lock == 0)
+ return;
+
+ slapi_lock_mutex(buffer_lock);
+
+
+ if(notify)
+ {
+ do
+ {
+ SCNotify *notify_next = notify->next;
+
+ if( slapi_utf8casecmp((unsigned char *)caller_id, (unsigned char *)notify->caller_id) )
+ {
+ notify->prev->next = notify->next;
+ notify->next->prev = notify->prev;
+
+ if(notify == head)
+ {
+ head = notify->next;
+ start_notify = notify->prev;
+ }
+
+ if(notify == head) /* must be the last item, turn off the lights */
+ head = 0;
+
+ if(callback)
+ callback(notify->caller_data);
+ slapi_ch_free_string(&notify->caller_id);
+ slapi_ch_free_string(&notify->dn);
+ slapi_ch_free_string(&notify->filter);
+ slapi_filter_free( notify->realfilter, 1 );
+ slapi_ch_free((void **)&notify);
+ }
+
+ notify = notify_next;
+ }
+ while(notify != start_notify && notify != NULL);
+ }
+
+ slapi_unlock_mutex(buffer_lock);
+}
+
+/* this func needs looking at to make work */
+static SCNotify *statechange_find_notify(char *dn, char *filter, notify_callback func)
+{
+ SCNotify *notify = head;
+ SCNotify *start_notify = head;
+
+ if(notify)
+ {
+ do
+ {
+ if( !slapi_utf8casecmp((unsigned char *)dn, (unsigned char *)notify->dn) &&
+ !slapi_utf8casecmp((unsigned char *)filter, (unsigned char *)notify->filter) && func == notify->func)
+ {
+ return notify;
+ }
+
+ notify = notify->next;
+ }
+ while(notify != start_notify);
+ }
+
+ return 0;
+}
+
+/* intended for use by vattr service providers
+ * to deal with significant vattr state changes
+ */
+static void _statechange_vattr_cache_invalidator_callback(Slapi_Entry *e, char *dn, int modtype, Slapi_PBlock *pb, void *caller_data)
+{
+ /* simply get the significance data and act */
+ switch(*(int*)caller_data)
+ {
+ case STATECHANGE_VATTR_ENTRY_INVALIDATE:
+ if(e)
+ slapi_entry_vattrcache_watermark_invalidate(e);
+ break;
+
+ case STATECHANGE_VATTR_GLOBAL_INVALIDATE:
+ default:
+ slapi_entrycache_vattrcache_watermark_invalidate();
+ break;
+ }
+}
+
diff --git a/ldap/servers/plugins/statechange/statechange.def b/ldap/servers/plugins/statechange/statechange.def
new file mode 100644
index 00000000..ed6dca6b
--- /dev/null
+++ b/ldap/servers/plugins/statechange/statechange.def
@@ -0,0 +1,10 @@
+; BEGIN COPYRIGHT BLOCK
+; Copyright 2001 Sun Microsystems, Inc.
+; Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+; All rights reserved.
+; END COPYRIGHT BLOCK
+;
+DESCRIPTION 'Netscape Directory Server 7.0 State Change Plugin'
+EXPORTS
+ statechange_init @2
+ plugin_init_debug_level @3
diff --git a/ldap/servers/plugins/syntaxes/Makefile b/ldap/servers/plugins/syntaxes/Makefile
new file mode 100644
index 00000000..edc9bd9c
--- /dev/null
+++ b/ldap/servers/plugins/syntaxes/Makefile
@@ -0,0 +1,87 @@
+#
+# BEGIN COPYRIGHT BLOCK
+# Copyright 2001 Sun Microsystems, Inc.
+# Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+# All rights reserved.
+# END COPYRIGHT BLOCK
+#
+#
+# GNU Makefile for Directory Server syntax-plugin.so syntax plugins
+#
+
+LDAP_SRC = ../../..
+MCOM_ROOT = ../../../../..
+
+NOSTDCLEAN=true # don't let nsconfig.mk define target clean
+NOSTDSTRIP=true # don't let nsconfig.mk define target strip
+NSPR20=true # probably should be defined somewhere else (not sure where)
+
+OBJDEST = $(OBJDIR)/lib/libsyntax
+LIBDIR = $(LIB_RELDIR)
+
+include $(MCOM_ROOT)/ldapserver/nsconfig.mk
+include $(LDAP_SRC)/nsldap.mk
+
+ifeq ($(ARCH), WINNT)
+DEF_FILE:=./libsyntax.def
+endif
+
+CFLAGS+=$(SLCFLAGS)
+
+INCLUDES += -I$(LDAP_SRC)/servers/slapd
+
+SYNTAX_OBJS= phonetic.o string.o cis.o sicis.o ces.o bin.o tel.o dn.o int.o \
+ value.o debug.o
+
+OBJS = $(addprefix $(OBJDEST)/, $(SYNTAX_OBJS))
+
+ifeq ($(ARCH), WINNT)
+LIBSYNTAX_DLL_OBJ = $(addprefix $(OBJDEST)/, dllmain.o)
+endif
+
+LIBSYNTAX= $(addprefix $(LIBDIR)/, $(SYNTAX_DLL).$(DLL_SUFFIX))
+
+ifeq ($(ARCH), WINNT)
+EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAP_LIBUTIL_DEP)
+EXTRA_LIBS_DEP += $(LDAPSDK_DEP)
+EXTRA_LIBS += $(LIBSLAPD) $(LDAP_SDK_LIBLDAP_DLL)
+endif
+ifeq ($(ARCH), AIX)
+EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAP_LIBUTIL_DEP)
+EXTRA_LIBS_DEP += $(LDAPSDK_DEP)
+EXTRA_LIBS += $(LIBSLAPD) $(LDAP_SDK_LIBLDAP_DLL)
+endif
+
+ifeq ($(ARCH), WINNT)
+DLL_LDFLAGS += -def:"./libsyntax.def"
+CFLAGS+= /WX
+endif # WINNT
+
+ifeq ($(ARCH), HPUX)
+EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAPSDK_DEP) $(NSPR_DEP) $(SECURITY_DEP)
+EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(SECURITYLINK) $(NSPRLINK) $(ICULINK)
+endif
+
+ifeq ($(ARCH), AIX)
+EXTRA_LIBS += $(DLL_EXTRA_LIBS)
+LD=ld
+endif
+
+clientSDK:
+
+all: $(OBJDEST) $(LIBDIR) $(LIBSYNTAX)
+
+$(LIBSYNTAX): $(OBJS) $(LIBSYNTAX_DLL_OBJ) $(DEF_FILE)
+ $(LINK_DLL) $(LIBSYNTAX_DLL_OBJ) $(EXTRA_LIBS)
+
+veryclean: clean
+
+clean:
+ $(RM) $(OBJS)
+ifeq ($(ARCH), WINNT)
+ $(RM) $(LIBSYNTAX_DLL_OBJ)
+endif
+ $(RM) $(LIBSYNTAX)
+
+$(OBJDEST):
+ $(MKDIR) $(OBJDEST)
diff --git a/ldap/servers/plugins/syntaxes/bin.c b/ldap/servers/plugins/syntaxes/bin.c
new file mode 100644
index 00000000..da06e55e
--- /dev/null
+++ b/ldap/servers/plugins/syntaxes/bin.c
@@ -0,0 +1,201 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* bin.c - bin syntax routines */
+
+/*
+ * This file actually implements two syntax plugins: OctetString and Binary.
+ * We treat them identically for now. XXXmcs: check if that is correct.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include "syntax.h"
+
+static int bin_filter_ava( Slapi_PBlock *pb, struct berval *bvfilter,
+ Slapi_Value **bvals, int ftype, Slapi_Value **retVal );
+static int bin_values2keys( Slapi_PBlock *pb, Slapi_Value **bvals,
+ Slapi_Value ***ivals, int ftype );
+static int bin_assertion2keys_ava( Slapi_PBlock *pb, Slapi_Value *bval,
+ Slapi_Value ***ivals, int ftype );
+
+/*
+ * Attribute syntaxes. We treat all of these the same for now, even though
+ * the specifications (e.g., RFC 2252) impose various constraints on the
+ * the format for each of these.
+ *
+ * Note: the first name is the official one from RFC 2252.
+ */
+static char *bin_names[] = { "Binary", "bin", BINARY_SYNTAX_OID, 0 };
+
+static char *octetstring_names[] = { "OctetString", OCTETSTRING_SYNTAX_OID, 0 };
+
+static char *jpeg_names[] = { "JPEG", JPEG_SYNTAX_OID, 0 };
+
+
+static Slapi_PluginDesc bin_pdesc = {
+ "bin-syntax", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT,
+ "binary attribute syntax plugin"
+};
+
+static Slapi_PluginDesc octetstring_pdesc = {
+ "octetstring-syntax", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT,
+ "octet string attribute syntax plugin"
+};
+
+static Slapi_PluginDesc jpeg_pdesc = {
+ "jpeg-syntax", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT,
+ "JPEG attribute syntax plugin"
+};
+
+
+/*
+ * register_bin_like_plugin(): register all items for a bin-like plugin.
+ */
+static int
+register_bin_like_plugin( Slapi_PBlock *pb, Slapi_PluginDesc *pdescp,
+ char **names, char *oid )
+{
+ int rc;
+
+ rc = slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION,
+ (void *) SLAPI_PLUGIN_VERSION_01 );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION,
+ (void *)pdescp );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_FILTER_AVA,
+ (void *) bin_filter_ava );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_VALUES2KEYS,
+ (void *) bin_values2keys );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_AVA,
+ (void *) bin_assertion2keys_ava );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_NAMES,
+ (void *) names );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_OID,
+ (void *) oid );
+
+ return( rc );
+}
+
+
+int
+bin_init( Slapi_PBlock *pb )
+{
+ int rc;
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "=> bin_init\n", 0, 0, 0 );
+ rc = register_bin_like_plugin( pb, &bin_pdesc, bin_names,
+ BINARY_SYNTAX_OID );
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "<= bin_init %d\n", rc, 0, 0 );
+ return( rc );
+}
+
+
+int
+octetstring_init( Slapi_PBlock *pb )
+{
+ int rc;
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "=> octetstring_init\n", 0, 0, 0 );
+ rc = register_bin_like_plugin( pb, &octetstring_pdesc, octetstring_names,
+ OCTETSTRING_SYNTAX_OID );
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "<= octetstring_init %d\n", rc, 0, 0 );
+ return( rc );
+}
+
+
+int
+jpeg_init( Slapi_PBlock *pb )
+{
+ int rc;
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "=> jpeg_init\n", 0, 0, 0 );
+ rc = register_bin_like_plugin( pb, &jpeg_pdesc, jpeg_names,
+ JPEG_SYNTAX_OID );
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "<= jpeg_init %d\n", rc, 0, 0 );
+ return( rc );
+}
+
+
+static int
+bin_filter_ava( Slapi_PBlock *pb, struct berval *bvfilter,
+ Slapi_Value **bvals, int ftype, Slapi_Value **retVal )
+{
+ int i;
+
+ for ( i = 0; bvals[i] != NULL; i++ ) {
+ if ( slapi_value_get_length(bvals[i]) == bvfilter->bv_len &&
+ 0 == memcmp( slapi_value_get_string(bvals[i]), bvfilter->bv_val, bvfilter->bv_len ))
+ {
+ if(retVal!=NULL)
+ {
+ *retVal= bvals[i];
+ }
+ return( 0 );
+ }
+ }
+ if(retVal!=NULL)
+ {
+ *retVal= NULL;
+ }
+ return( -1 );
+}
+
+static int
+bin_values2keys( Slapi_PBlock *pb, Slapi_Value **bvals,
+ Slapi_Value ***ivals, int ftype )
+{
+ int i;
+
+ if ( ftype != LDAP_FILTER_EQUALITY ) {
+ return( LDAP_PROTOCOL_ERROR );
+ }
+
+ for ( i = 0; bvals[i] != NULL; i++ ) {
+ /* NULL */
+ }
+ (*ivals) = (Slapi_Value **) slapi_ch_malloc(( i + 1 ) *
+ sizeof(Slapi_Value *) );
+
+ for ( i = 0; bvals[i] != NULL; i++ )
+ {
+ (*ivals)[i] = slapi_value_dup(bvals[i]);
+ }
+ (*ivals)[i] = NULL;
+
+ return( 0 );
+}
+
+static int
+bin_assertion2keys_ava( Slapi_PBlock *pb, Slapi_Value *bval,
+ Slapi_Value ***ivals, int ftype )
+{
+ Slapi_Value *tmpval=NULL;
+ size_t len;
+
+ if (( ftype != LDAP_FILTER_EQUALITY ) &&
+ ( ftype != LDAP_FILTER_EQUALITY_FAST))
+ {
+ return( LDAP_PROTOCOL_ERROR );
+ }
+ if(ftype == LDAP_FILTER_EQUALITY_FAST) {
+ /* With the fast option, we are trying to avoid creating and freeing
+ * a bunch of structures - we just do one malloc here - see
+ * ava_candidates in filterentry.c
+ */
+ len=slapi_value_get_length(bval);
+ tmpval=(*ivals)[0];
+ if (len > tmpval->bv.bv_len) {
+ tmpval->bv.bv_val=(char *)slapi_ch_malloc(len);
+ }
+ tmpval->bv.bv_len=len;
+ memcpy(tmpval->bv.bv_val,slapi_value_get_string(bval),len);
+ } else {
+ (*ivals) = (Slapi_Value **) slapi_ch_malloc( 2 * sizeof(Slapi_Value *) );
+ (*ivals)[0] = slapi_value_dup( bval );
+ (*ivals)[1] = NULL;
+ }
+ return( 0 );
+}
diff --git a/ldap/servers/plugins/syntaxes/ces.c b/ldap/servers/plugins/syntaxes/ces.c
new file mode 100644
index 00000000..075da03b
--- /dev/null
+++ b/ldap/servers/plugins/syntaxes/ces.c
@@ -0,0 +1,168 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* ces.c - caseexactstring syntax routines */
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include "syntax.h"
+
+static int ces_filter_ava( Slapi_PBlock *pb, struct berval *bvfilter,
+ Slapi_Value **bvals, int ftype, Slapi_Value **retVal );
+static int ces_filter_sub( Slapi_PBlock *pb, char *initial, char **any,
+ char *final, Slapi_Value **bvals );
+static int ces_values2keys( Slapi_PBlock *pb, Slapi_Value **val,
+ Slapi_Value ***ivals, int ftype );
+static int ces_assertion2keys_ava( Slapi_PBlock *pb, Slapi_Value *val,
+ Slapi_Value ***ivals, int ftype );
+static int ces_assertion2keys_sub( Slapi_PBlock *pb, char *initial, char **any,
+ char *final, Slapi_Value ***ivals );
+static int ces_compare(struct berval *v1, struct berval *v2);
+
+/* the first name is the official one from RFC 2252 */
+static char *ia5_names[] = { "IA5String", "ces", "caseexactstring",
+ IA5STRING_SYNTAX_OID, 0 };
+
+/* the first name is the official one from RFC 2252 */
+static char *uri_names[] = { "URI", "1.3.6.1.4.1.4401.1.1.1",0};
+
+static Slapi_PluginDesc ia5_pdesc = { "ces-syntax", PLUGIN_MAGIC_VENDOR_STR,
+ PRODUCTTEXT, "caseExactString attribute syntax plugin" };
+
+static Slapi_PluginDesc uri_pdesc = { "uri-syntax", PLUGIN_MAGIC_VENDOR_STR,
+ PRODUCTTEXT, "uri attribute syntax plugin" };
+
+
+/*
+ * register_ces_like_plugin(): register all items for a cis-like plugin.
+ */
+static int
+register_ces_like_plugin( Slapi_PBlock *pb, Slapi_PluginDesc *pdescp,
+ char **names, char *oid )
+{
+ int rc, flags;
+
+ rc = slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION,
+ (void *) SLAPI_PLUGIN_VERSION_01 );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION,
+ (void *) pdescp );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_FILTER_AVA,
+ (void *) ces_filter_ava );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_FILTER_SUB,
+ (void *) ces_filter_sub );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_VALUES2KEYS,
+ (void *) ces_values2keys );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_AVA,
+ (void *) ces_assertion2keys_ava );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_SUB,
+ (void *) ces_assertion2keys_sub );
+ flags = SLAPI_PLUGIN_SYNTAX_FLAG_ORDERING;
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_FLAGS,
+ (void *) &flags );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_NAMES,
+ (void *) names );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_OID,
+ (void *) oid );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_COMPARE,
+ (void *) ces_compare );
+
+ return( rc );
+}
+
+int
+ces_init( Slapi_PBlock *pb )
+{
+ int rc;
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "=> ces_init\n", 0, 0, 0 );
+
+ rc = register_ces_like_plugin(pb,&ia5_pdesc,ia5_names,IA5STRING_SYNTAX_OID);
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "<= ces_init %d\n", rc, 0, 0 );
+ return( rc );
+}
+
+int
+uri_init( Slapi_PBlock *pb )
+{
+ int rc;
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "=> uri_init\n", 0, 0, 0 );
+
+ rc = register_ces_like_plugin(pb,&uri_pdesc,uri_names,
+ "1.3.6.1.4.1.4401.1.1.1");
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "<= uri_init %d\n", rc, 0, 0 );
+ return( rc );
+}
+
+static int
+ces_filter_ava(
+ Slapi_PBlock *pb,
+ struct berval *bvfilter,
+ Slapi_Value **bvals,
+ int ftype,
+ Slapi_Value **retVal
+)
+{
+ return( string_filter_ava( bvfilter, bvals, SYNTAX_CES, ftype,
+ retVal) );
+}
+
+static int
+ces_filter_sub(
+ Slapi_PBlock *pb,
+ char *initial,
+ char **any,
+ char *final,
+ Slapi_Value **bvals
+)
+{
+ return( string_filter_sub( pb, initial, any, final, bvals, SYNTAX_CES ) );
+}
+
+static int
+ces_values2keys(
+ Slapi_PBlock *pb,
+ Slapi_Value **vals,
+ Slapi_Value ***ivals,
+ int ftype
+)
+{
+ return( string_values2keys( pb, vals, ivals, SYNTAX_CES, ftype ) );
+}
+
+static int
+ces_assertion2keys_ava(
+ Slapi_PBlock *pb,
+ Slapi_Value *val,
+ Slapi_Value ***ivals,
+ int ftype
+)
+{
+ return(string_assertion2keys_ava( pb, val, ivals, SYNTAX_CES, ftype ));
+}
+
+static int
+ces_assertion2keys_sub(
+ Slapi_PBlock *pb,
+ char *initial,
+ char **any,
+ char *final,
+ Slapi_Value ***ivals
+)
+{
+ return( string_assertion2keys_sub( pb, initial, any, final, ivals,
+ SYNTAX_CES ) );
+}
+
+static int ces_compare(
+ struct berval *v1,
+ struct berval *v2
+)
+{
+ return value_cmp(v1,v2,SYNTAX_CES,3 /* Normalise both values */);
+}
diff --git a/ldap/servers/plugins/syntaxes/cis.c b/ldap/servers/plugins/syntaxes/cis.c
new file mode 100644
index 00000000..e2047fbc
--- /dev/null
+++ b/ldap/servers/plugins/syntaxes/cis.c
@@ -0,0 +1,298 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* cis.c - caseignorestring syntax routines */
+
+/*
+ * This file actually implements three syntax plugins:
+ * DirectoryString
+ * Boolean
+ * GeneralizedTime
+ *
+ * We treat them identically for now. XXXmcs: we could do some validation on
+ * Boolean and GeneralizedTime values (someday, maybe).
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include "syntax.h"
+
+static int cis_filter_ava( Slapi_PBlock *pb, struct berval *bvfilter,
+ Slapi_Value **bvals, int ftype, Slapi_Value **retVal );
+static int cis_filter_sub( Slapi_PBlock *pb, char *initial, char **any,
+ char *final, Slapi_Value **bvals );
+static int cis_values2keys( Slapi_PBlock *pb, Slapi_Value **val,
+ Slapi_Value ***ivals, int ftype );
+static int cis_assertion2keys_ava( Slapi_PBlock *pb, Slapi_Value *val,
+ Slapi_Value ***ivals, int ftype );
+static int cis_assertion2keys_sub( Slapi_PBlock *pb, char *initial, char **any,
+ char *final, Slapi_Value ***ivals );
+static int cis_compare(struct berval *v1, struct berval *v2);
+
+/*
+ * Attribute syntaxes. We treat all of these the same for now, even though
+ * the specifications (e.g., RFC 2252) impose various constraints on the
+ * the format for each of these.
+ *
+ * Note: the first name is the official one from RFC 2252.
+ */
+static char *dirstring_names[] = { "DirectoryString", "cis",
+ "caseignorestring", DIRSTRING_SYNTAX_OID, 0 };
+
+static char *boolean_names[] = { "Boolean", BOOLEAN_SYNTAX_OID, 0 };
+
+static char *time_names[] = { "GeneralizedTime", "time",
+ GENERALIZEDTIME_SYNTAX_OID, 0 };
+
+static char *country_names[] = { "Country String",
+ COUNTRYSTRING_SYNTAX_OID, 0};
+
+static char *postal_names[] = { "Postal Address",
+ POSTALADDRESS_SYNTAX_OID, 0};
+
+static char *oid_names[] = { "OID",
+ OID_SYNTAX_OID, 0};
+
+
+/*
+ TBD (XXX)
+
+ "1.3.6.1.4.1.1466.115.121.1.16 \"DIT Content Rule Description
+\" "
+ "1.3.6.1.4.1.1466.115.121.1.17 \"DIT Structure Rule Descripti
+on\" "
+ "1.3.6.1.4.1.1466.115.121.1.20 \"DSE Type\" "
+ "1.3.6.1.4.1.1466.115.121.1.30 \"Matching Rule Description\"
+"
+ "1.3.6.1.4.1.1466.115.121.1.31 \"Matching Rule Use Descriptio
+n\" "
+ "1.3.6.1.4.1.1466.115.121.1.35 \"Name Form Description\" "
+
+ "1.3.6.1.4.1.1466.115.121.1.44 \"Printable String\" "
+ "1.3.6.1.4.1.1466.115.121.1.45 \"Subtree Specification\" "
+ "1.3.6.1.4.1.1466.115.121.1.54 \"LDAP Syntax Description\" "
+ "1.3.6.1.4.1.1466.115.121.1.55 \"Modify Rights\" "
+ "1.3.6.1.4.1.1466.115.121.1.56 \"LDAP Schema Description\" "
+ "1.3.6.1.4.1.1466.115.121.1.25 \"Guide\" "
+ "1.3.6.1.4.1.1466.115.121.1.52 \"Telex Number\" "
+ "1.3.6.1.4.1.1466.115.121.1.51 \"Teletex Terminal Identifier\
+" "
+ "1.3.6.1.4.1.1466.115.121.1.14 \"Delivery Method\" "
+ "1.3.6.1.4.1.1466.115.121.1.43 \"Presentation Address\" "
+ "1.3.6.1.4.1.1466.115.121.1.21 \"Enhanced Guide\" "
+ "1.3.6.1.4.1.1466.115.121.1.34 \"Name and Optional UID\" "
+ "1.2.840.113556.1.4.905 \"CaseIgnoreString\" "
+ "1.3.6.1.1.1.0.0 \"nisNetgroupTripleSyntax\" "
+ "1.3.6.1.1.1.0.1 \"bootParameterSyntax\" ");
+ */
+
+
+static Slapi_PluginDesc dirstring_pdesc = { "directorystring-syntax",
+ PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT,
+ "DirectoryString attribute syntax plugin" };
+
+static Slapi_PluginDesc boolean_pdesc = { "boolean-syntax",
+ PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT,
+ "Boolean attribute syntax plugin" };
+
+static Slapi_PluginDesc time_pdesc = { "time-syntax",
+ PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT,
+ "GeneralizedTime attribute syntax plugin" };
+
+static Slapi_PluginDesc country_pdesc = { "countrystring-syntax",
+ PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT,
+ "Country String attribute syntax plugin" };
+
+static Slapi_PluginDesc postal_pdesc = { "postaladdress-syntax",
+ PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT,
+ "Postal Address attribute syntax plugin" };
+
+static Slapi_PluginDesc oid_pdesc = { "oid-syntax",
+ PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT,
+ "OID attribute syntax plugin" };
+
+
+/*
+ * register_cis_like_plugin(): register all items for a cis-like plugin.
+ */
+static int
+register_cis_like_plugin( Slapi_PBlock *pb, Slapi_PluginDesc *pdescp,
+ char **names, char *oid )
+{
+ int rc, flags;
+
+ rc = slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION,
+ (void *) SLAPI_PLUGIN_VERSION_01 );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION,
+ (void *) pdescp );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_FILTER_AVA,
+ (void *) cis_filter_ava );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_FILTER_SUB,
+ (void *) cis_filter_sub );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_VALUES2KEYS,
+ (void *) cis_values2keys );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_AVA,
+ (void *) cis_assertion2keys_ava );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_SUB,
+ (void *) cis_assertion2keys_sub );
+ flags = SLAPI_PLUGIN_SYNTAX_FLAG_ORDERING;
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_FLAGS,
+ (void *) &flags );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_NAMES,
+ (void *) names );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_OID,
+ (void *) oid );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_COMPARE,
+ (void *) cis_compare );
+
+ return( rc );
+}
+
+
+int
+cis_init( Slapi_PBlock *pb )
+{
+ int rc;
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "=> cis_init\n", 0, 0, 0 );
+ rc = register_cis_like_plugin( pb, &dirstring_pdesc, dirstring_names,
+ DIRSTRING_SYNTAX_OID );
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "<= cis_init %d\n", rc, 0, 0 );
+ return( rc );
+}
+
+
+int
+boolean_init( Slapi_PBlock *pb )
+{
+ int rc;
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "=> boolean_init\n", 0, 0, 0 );
+ rc = register_cis_like_plugin( pb, &boolean_pdesc, boolean_names,
+ BOOLEAN_SYNTAX_OID );
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "<= boolean_init %d\n", rc, 0, 0 );
+ return( rc );
+}
+
+
+int
+time_init( Slapi_PBlock *pb )
+{
+ int rc;
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "=> time_init\n", 0, 0, 0 );
+ rc = register_cis_like_plugin( pb, &time_pdesc, time_names,
+ GENERALIZEDTIME_SYNTAX_OID );
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "<= time_init %d\n", rc, 0, 0 );
+ return( rc );
+}
+
+int
+country_init( Slapi_PBlock *pb )
+{
+ int rc;
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "=> country_init\n", 0, 0, 0 );
+ rc = register_cis_like_plugin( pb, &country_pdesc, country_names,
+ COUNTRYSTRING_SYNTAX_OID );
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "<= country_init %d\n", rc, 0, 0 );
+ return( rc );
+}
+
+int
+postal_init( Slapi_PBlock *pb )
+{
+ int rc;
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "=> postal_init\n", 0, 0, 0 );
+ rc = register_cis_like_plugin( pb, &postal_pdesc, postal_names,
+ POSTALADDRESS_SYNTAX_OID );
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "<= postal_init %d\n", rc, 0, 0 );
+ return( rc );
+}
+
+
+int
+oid_init( Slapi_PBlock *pb )
+{
+ int rc;
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "=> oid_init\n", 0, 0, 0 );
+ rc = register_cis_like_plugin( pb, &oid_pdesc, oid_names, OID_SYNTAX_OID );
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "<= oid_init %d\n", rc, 0, 0 );
+ return( rc );
+}
+
+
+
+static int
+cis_filter_ava(
+ Slapi_PBlock *pb,
+ struct berval *bvfilter,
+ Slapi_Value **bvals,
+ int ftype,
+ Slapi_Value **retVal
+)
+{
+ return( string_filter_ava( bvfilter, bvals, SYNTAX_CIS, ftype,
+ retVal ) );
+}
+
+
+static int
+cis_filter_sub(
+ Slapi_PBlock *pb,
+ char *initial,
+ char **any,
+ char *final,
+ Slapi_Value **bvals
+)
+{
+ return( string_filter_sub( pb, initial, any, final, bvals, SYNTAX_CIS ) );
+}
+
+static int
+cis_values2keys(
+ Slapi_PBlock *pb,
+ Slapi_Value **vals,
+ Slapi_Value ***ivals,
+ int ftype
+)
+{
+ return( string_values2keys( pb, vals, ivals, SYNTAX_CIS, ftype ) );
+}
+
+static int
+cis_assertion2keys_ava(
+ Slapi_PBlock *pb,
+ Slapi_Value *val,
+ Slapi_Value ***ivals,
+ int ftype
+)
+{
+ return(string_assertion2keys_ava( pb, val, ivals, SYNTAX_CIS, ftype ));
+}
+
+static int
+cis_assertion2keys_sub(
+ Slapi_PBlock *pb,
+ char *initial,
+ char **any,
+ char *final,
+ Slapi_Value ***ivals
+)
+{
+ return( string_assertion2keys_sub( pb, initial, any, final, ivals,
+ SYNTAX_CIS ) );
+}
+
+static int cis_compare(
+ struct berval *v1,
+ struct berval *v2
+)
+{
+ return value_cmp(v1,v2,SYNTAX_CIS,3 /* Normalise both values */);
+}
diff --git a/ldap/servers/plugins/syntaxes/debug.c b/ldap/servers/plugins/syntaxes/debug.c
new file mode 100644
index 00000000..e4d69202
--- /dev/null
+++ b/ldap/servers/plugins/syntaxes/debug.c
@@ -0,0 +1,20 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* debug.c - syntax debug stuff */
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include "syntax.h"
+
+#ifdef _WIN32
+int *module_ldap_debug = 0;
+
+void plugin_init_debug_level(int *level_ptr)
+{
+ module_ldap_debug = level_ptr;
+}
+#endif
diff --git a/ldap/servers/plugins/syntaxes/dllmain.c b/ldap/servers/plugins/syntaxes/dllmain.c
new file mode 100644
index 00000000..1f7aa331
--- /dev/null
+++ b/ldap/servers/plugins/syntaxes/dllmain.c
@@ -0,0 +1,133 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/*
+ * Microsoft Windows specifics for syntax-plugin DLL
+ */
+#include "ldap.h"
+#include "syntax.h"
+
+
+#ifdef _WIN32
+/* Lifted from Q125688
+ * How to Port a 16-bit DLL to a Win32 DLL
+ * on the MSVC 4.0 CD
+ */
+BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved)
+{
+ WSADATA wsadata;
+
+ switch (fdwReason)
+ {
+ case DLL_PROCESS_ATTACH:
+ /* Code from LibMain inserted here. Return TRUE to keep the
+ DLL loaded or return FALSE to fail loading the DLL.
+
+ You may have to modify the code in your original LibMain to
+ account for the fact that it may be called more than once.
+ You will get one DLL_PROCESS_ATTACH for each process that
+ loads the DLL. This is different from LibMain which gets
+ called only once when the DLL is loaded. The only time this
+ is critical is when you are using shared data sections.
+ If you are using shared data sections for statically
+ allocated data, you will need to be careful to initialize it
+ only once. Check your code carefully.
+
+ Certain one-time initializations may now need to be done for
+ each process that attaches. You may also not need code from
+ your original LibMain because the operating system may now
+ be doing it for you.
+ */
+ /*
+ * 16 bit code calls UnlockData()
+ * which is mapped to UnlockSegment in windows.h
+ * in 32 bit world UnlockData is not defined anywhere
+ * UnlockSegment is mapped to GlobalUnfix in winbase.h
+ * and the docs for both UnlockSegment and GlobalUnfix say
+ * ".. function is oboslete. Segments have no meaning
+ * in the 32-bit environment". So we do nothing here.
+ */
+
+ if( errno = WSAStartup(0x0101, &wsadata ) != 0 )
+ return FALSE;
+
+ break;
+
+ case DLL_THREAD_ATTACH:
+ /* Called each time a thread is created in a process that has
+ already loaded (attached to) this DLL. Does not get called
+ for each thread that exists in the process before it loaded
+ the DLL.
+
+ Do thread-specific initialization here.
+ */
+ break;
+
+ case DLL_THREAD_DETACH:
+ /* Same as above, but called when a thread in the process
+ exits.
+
+ Do thread-specific cleanup here.
+ */
+ break;
+
+ case DLL_PROCESS_DETACH:
+ /* Code from _WEP inserted here. This code may (like the
+ LibMain) not be necessary. Check to make certain that the
+ operating system is not doing it for you.
+ */
+ WSACleanup();
+
+ break;
+ }
+ /* The return value is only used for DLL_PROCESS_ATTACH; all other
+ conditions are ignored. */
+ return TRUE; // successful DLL_PROCESS_ATTACH
+}
+#else
+int CALLBACK
+LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine )
+{
+ /*UnlockData( 0 );*/
+ return( 1 );
+}
+#endif
+
+#ifdef LDAP_DEBUG
+#ifndef _WIN32
+#include <stdarg.h>
+#include <stdio.h>
+
+void LDAPDebug( int level, char* fmt, ... )
+{
+ static char debugBuf[1024];
+
+ if (module_ldap_debug && (*module_ldap_debug & level))
+ {
+ va_list ap;
+ va_start (ap, fmt);
+ _snprintf (debugBuf, sizeof(debugBuf), fmt, ap);
+ va_end (ap);
+
+ OutputDebugString (debugBuf);
+ }
+}
+#endif
+#endif
+
+#ifndef _WIN32
+
+/* The 16-bit version of the RTL does not implement perror() */
+
+#include <stdio.h>
+
+void perror( const char *msg )
+{
+ char buf[128];
+ wsprintf( buf, "%s: error %d\n", msg, WSAGetLastError()) ;
+ OutputDebugString( buf );
+}
+
+#endif
diff --git a/ldap/servers/plugins/syntaxes/dn.c b/ldap/servers/plugins/syntaxes/dn.c
new file mode 100644
index 00000000..e6a90df0
--- /dev/null
+++ b/ldap/servers/plugins/syntaxes/dn.c
@@ -0,0 +1,98 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* dn.c - dn syntax routines */
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include "syntax.h"
+
+static int dn_filter_ava( Slapi_PBlock *pb, struct berval *bvfilter,
+ Slapi_Value **bvals, int ftype, Slapi_Value **retVal );
+static int dn_filter_sub( Slapi_PBlock *pb, char *initial, char **any,
+ char *final, Slapi_Value **bvals );
+static int dn_values2keys( Slapi_PBlock *pb, Slapi_Value **vals,
+ Slapi_Value ***ivals, int ftype );
+static int dn_assertion2keys_ava( Slapi_PBlock *pb, Slapi_Value *val,
+ Slapi_Value ***ivals, int ftype );
+static int dn_assertion2keys_sub( Slapi_PBlock *pb, char *initial, char **any,
+ char *final, Slapi_Value ***ivals );
+
+/* the first name is the official one from RFC 2252 */
+static char *names[] = { "DN", DN_SYNTAX_OID, 0 };
+
+static Slapi_PluginDesc pdesc = { "dn-syntax", PLUGIN_MAGIC_VENDOR_STR,
+ PRODUCTTEXT, "distinguished name attribute syntax plugin" };
+
+int
+dn_init( Slapi_PBlock *pb )
+{
+ int rc;
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "=> dn_init\n", 0, 0, 0 );
+
+ rc = slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION,
+ (void *) SLAPI_PLUGIN_VERSION_01 );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION,
+ (void *)&pdesc );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_FILTER_AVA,
+ (void *) dn_filter_ava );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_FILTER_SUB,
+ (void *) dn_filter_sub );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_VALUES2KEYS,
+ (void *) dn_values2keys );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_AVA,
+ (void *) dn_assertion2keys_ava );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_SUB,
+ (void *) dn_assertion2keys_sub );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_NAMES,
+ (void *) names );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_OID,
+ (void *) DN_SYNTAX_OID );
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "<= dn_init %d\n", rc, 0, 0 );
+ return( rc );
+}
+
+static int
+dn_filter_ava( Slapi_PBlock *pb, struct berval *bvfilter,
+ Slapi_Value **bvals, int ftype, Slapi_Value **retVal )
+{
+ return( string_filter_ava( bvfilter, bvals, SYNTAX_CIS | SYNTAX_DN,
+ ftype, retVal ) );
+}
+
+static int
+dn_filter_sub( Slapi_PBlock *pb, char *initial, char **any, char *final,
+ Slapi_Value **bvals )
+{
+ return( string_filter_sub( pb, initial, any, final, bvals,
+ SYNTAX_CIS | SYNTAX_DN ) );
+}
+
+static int
+dn_values2keys( Slapi_PBlock *pb, Slapi_Value **vals, Slapi_Value ***ivals,
+ int ftype )
+{
+ return( string_values2keys( pb, vals, ivals, SYNTAX_CIS | SYNTAX_DN,
+ ftype ) );
+}
+
+static int
+dn_assertion2keys_ava( Slapi_PBlock *pb, Slapi_Value *val,
+ Slapi_Value ***ivals, int ftype )
+{
+ return( string_assertion2keys_ava( pb, val, ivals,
+ SYNTAX_CIS | SYNTAX_DN, ftype ) );
+}
+
+static int
+dn_assertion2keys_sub( Slapi_PBlock *pb, char *initial, char **any, char *final,
+ Slapi_Value ***ivals )
+{
+ return( string_assertion2keys_sub( pb, initial, any, final, ivals,
+ SYNTAX_CIS | SYNTAX_DN ) );
+}
diff --git a/ldap/servers/plugins/syntaxes/int.c b/ldap/servers/plugins/syntaxes/int.c
new file mode 100644
index 00000000..f81167f2
--- /dev/null
+++ b/ldap/servers/plugins/syntaxes/int.c
@@ -0,0 +1,188 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* int.c - integer syntax routines */
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include "syntax.h"
+
+static int int_filter_ava( Slapi_PBlock *pb, struct berval *bvfilter,
+ Slapi_Value **bvals, int ftype, Slapi_Value **retVal );
+static int int_values2keys( Slapi_PBlock *pb, Slapi_Value **val,
+ Slapi_Value ***ivals );
+static int int_assertion2keys( Slapi_PBlock *pb, Slapi_Value *val,
+ Slapi_Value ***ivals, int ftype );
+static int int_compare(struct berval *v1, struct berval *v2);
+static long int_to_canonical( long num );
+
+/* the first name is the official one from RFC 2252 */
+static char *names[] = { "INTEGER", "int", INTEGER_SYNTAX_OID, 0 };
+
+static Slapi_PluginDesc pdesc = { "int-syntax", PLUGIN_MAGIC_VENDOR_STR,
+ PRODUCTTEXT, "integer attribute syntax plugin" };
+
+int
+int_init( Slapi_PBlock *pb )
+{
+ int rc, flags;
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "=> int_init\n", 0, 0, 0 );
+
+ rc = slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION,
+ (void *) SLAPI_PLUGIN_VERSION_01 );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION,
+ (void *)&pdesc );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_FILTER_AVA,
+ (void *) int_filter_ava );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_VALUES2KEYS,
+ (void *) int_values2keys );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_AVA,
+ (void *) int_assertion2keys );
+ flags = SLAPI_PLUGIN_SYNTAX_FLAG_ORDERING;
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_FLAGS,
+ (void *) &flags );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_NAMES,
+ (void *) names );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_OID,
+ (void *) INTEGER_SYNTAX_OID );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_COMPARE,
+ (void *) int_compare );
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "<= int_init %d\n", rc, 0, 0 );
+ return( rc );
+}
+
+static int
+int_filter_ava( Slapi_PBlock *pb, struct berval *bvfilter,
+ Slapi_Value **bvals, int ftype, Slapi_Value **retVal )
+{
+ int i, rc;
+ long flong, elong;
+
+ if ( ftype == LDAP_FILTER_APPROX ) {
+ return( LDAP_PROTOCOL_ERROR );
+ }
+ if(retVal) {
+ *retVal=NULL;
+ }
+ flong = atol( bvfilter->bv_val );
+ for ( i = 0; bvals[i] != NULL; i++ ) {
+ elong = atol ( slapi_value_get_string(bvals[i]) );
+ rc = elong - flong;
+ switch ( ftype ) {
+ case LDAP_FILTER_GE:
+ if ( rc >= 0 ) {
+ if(retVal) {
+ *retVal = bvals[i];
+ }
+ return( 0 );
+ }
+ break;
+ case LDAP_FILTER_LE:
+ if ( rc <= 0 ) {
+ if(retVal) {
+ *retVal = bvals[i];
+ }
+ return( 0 );
+ }
+ break;
+ case LDAP_FILTER_EQUALITY:
+ if ( rc == 0 ) {
+ if(retVal) {
+ *retVal = bvals[i];
+ }
+ return( 0 );
+ }
+ break;
+ }
+ }
+
+ return( -1 );
+}
+
+static int
+int_values2keys( Slapi_PBlock *pb, Slapi_Value **vals, Slapi_Value ***ivals )
+{
+ long num;
+ int i;
+
+ for ( i = 0; vals[i] != NULL; i++ ) {
+ /* NULL */
+ }
+
+ *ivals = (Slapi_Value **) slapi_ch_malloc(( i + 1 ) * sizeof(Slapi_Value *) );
+
+ for ( i = 0; vals[i] != NULL; i++ )
+ {
+ num = atol( slapi_value_get_string(vals[i]) );
+ num = int_to_canonical( num );
+ (*ivals)[i] = slapi_value_new();
+ slapi_value_set((*ivals)[i],&num,sizeof(long));
+ }
+ (*ivals)[i] = NULL;
+
+ return( 0 );
+}
+
+static int
+int_assertion2keys( Slapi_PBlock *pb, Slapi_Value *val, Slapi_Value ***ivals, int ftype )
+{
+ long num;
+ size_t len;
+ unsigned char *b;
+ Slapi_Value *tmpval=NULL;
+
+ num = atol( slapi_value_get_string(val) );
+ num = int_to_canonical( num );
+ /* similar to string.c to optimize equality path: avoid malloc/free */
+ if(ftype == LDAP_FILTER_EQUALITY_FAST) {
+ len=sizeof(long);
+ tmpval=(*ivals)[0];
+ if ( len > tmpval->bv.bv_len) {
+ tmpval->bv.bv_val=(char *)slapi_ch_malloc(len);
+ }
+ tmpval->bv.bv_len=len;
+ b = (unsigned char *)&num;
+ memcpy(tmpval->bv.bv_val,b,len);
+ } else {
+ *ivals = (Slapi_Value **) slapi_ch_malloc( 2 * sizeof(Slapi_Value *) );
+ (*ivals)[0] = (Slapi_Value *) slapi_ch_malloc( sizeof(Slapi_Value) );
+ /* XXXSD initialize memory */
+ memset((*ivals)[0],0,sizeof(Slapi_Value));
+ slapi_value_set((*ivals)[0],&num,sizeof(long));
+ (*ivals)[1] = NULL;
+ }
+ return( 0 );
+}
+
+static int int_compare(
+ struct berval *v1,
+ struct berval *v2
+)
+{
+ long value1 = atol(v1->bv_val);
+ long value2 = atol(v2->bv_val);
+
+ if (value1 == value2) {
+ return 0;
+ }
+ return ( ((value1 - value2) > 0) ? 1 : -1);
+}
+
+static long
+int_to_canonical( long num )
+{
+ long ret = 0L;
+ unsigned char *b = (unsigned char *)&ret;
+
+ b[0] = (unsigned char)(num >> 24);
+ b[1] = (unsigned char)(num >> 16);
+ b[2] = (unsigned char)(num >> 8);
+ b[3] = (unsigned char)num;
+
+ return ret;
+}
diff --git a/ldap/servers/plugins/syntaxes/libsyntax.def b/ldap/servers/plugins/syntaxes/libsyntax.def
new file mode 100644
index 00000000..30cb6b40
--- /dev/null
+++ b/ldap/servers/plugins/syntaxes/libsyntax.def
@@ -0,0 +1,24 @@
+; BEGIN COPYRIGHT BLOCK
+; Copyright 2001 Sun Microsystems, Inc.
+; Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+; All rights reserved.
+; END COPYRIGHT BLOCK
+;
+DESCRIPTION 'Directory Server 7 syntaxes Plugin'
+EXPORTS
+ cis_init @2
+ ces_init @3
+ tel_init @4
+ dn_init @5
+ bin_init @6
+ int_init @7
+ plugin_init_debug_level @8
+ octetstring_init @9
+ boolean_init @10
+ time_init @11
+ uri_init @12
+ country_init @13
+ postal_init @14
+ jpeg_init @15
+ oid_init @16
+ sicis_init @17
diff --git a/ldap/servers/plugins/syntaxes/phonetic.c b/ldap/servers/plugins/syntaxes/phonetic.c
new file mode 100644
index 00000000..1c1c8ba5
--- /dev/null
+++ b/ldap/servers/plugins/syntaxes/phonetic.c
@@ -0,0 +1,461 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* phonetic.c - routines to do phonetic matching */
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include "syntax.h"
+#include "portable.h"
+
+#if !defined(METAPHONE) && !defined(SOUNDEX)
+#define METAPHONE
+#endif
+
+#define iswordbreak(s) \
+(isascii(*(s)) \
+? (isspace(*(s)) || \
+ ispunct(*(s)) || \
+ isdigit(*(s)) || \
+ *(s) == '\0') \
+: utf8iswordbreak(s))
+
+static int
+utf8iswordbreak( const char* s )
+{
+ switch( LDAP_UTF8GETCC( s )) {
+ case 0x00A0: /* non-breaking space */
+ case 0x3000: /* ideographic space */
+ case 0xFEFF: /* zero-width non-breaking space */
+ return 1;
+ default: break;
+ }
+ return 0;
+}
+
+char *
+first_word( char *s )
+{
+ if ( s == NULL ) {
+ return( NULL );
+ }
+
+ while ( iswordbreak( s ) ) {
+ if ( *s == '\0' ) {
+ return( NULL );
+ } else {
+ LDAP_UTF8INC( s );
+ }
+ }
+
+ return( s );
+}
+
+char *
+next_word( char *s )
+{
+ if ( s == NULL ) {
+ return( NULL );
+ }
+
+ while ( ! iswordbreak( s ) ) {
+ LDAP_UTF8INC( s );
+ }
+
+ while ( iswordbreak( s ) ) {
+ if ( *s == '\0' ) {
+ return( NULL );
+ } else {
+ LDAP_UTF8INC( s );
+ }
+ }
+
+ return( s );
+}
+
+char *
+word_dup( char *w )
+{
+ char *s, *ret;
+ char save;
+
+ for ( s = w; !iswordbreak( s ); LDAP_UTF8INC( s ))
+ ; /* NULL */
+ save = *s;
+ *s = '\0';
+ ret = slapi_ch_strdup( w );
+ *s = save;
+
+ return( ret );
+}
+
+#ifndef MAXPHONEMELEN
+#define MAXPHONEMELEN 4
+#endif
+
+#if defined(SOUNDEX)
+
+/* lifted from isode-8.0 */
+char *
+phonetic( char *s )
+{
+ char code, adjacent, ch;
+ char *p;
+ char **c;
+ int i, cmax;
+ char phoneme[MAXPHONEMELEN + 1];
+
+ p = s;
+ if ( p == NULL || *p == '\0' ) {
+ return( NULL );
+ }
+
+ adjacent = '0';
+ phoneme[0] = TOUPPER(*p);
+
+ phoneme[1] = '\0';
+ for ( i = 0; i < 99 && (! iswordbreak(p)); LDAP_UTF8INC( p )) {
+ ch = TOUPPER (*p);
+
+ code = '0';
+
+ switch (ch) {
+ case 'B':
+ case 'F':
+ case 'P':
+ case 'V':
+ code = (adjacent != '1') ? '1' : '0';
+ break;
+ case 'S':
+ case 'C':
+ case 'G':
+ case 'J':
+ case 'K':
+ case 'Q':
+ case 'X':
+ case 'Z':
+ code = (adjacent != '2') ? '2' : '0';
+ break;
+ case 'D':
+ case 'T':
+ code = (adjacent != '3') ? '3' : '0';
+ break;
+ case 'L':
+ code = (adjacent != '4') ? '4' : '0';
+ break;
+ case 'M':
+ case 'N':
+ code = (adjacent != '5') ? '5' : '0';
+ break;
+ case 'R':
+ code = (adjacent != '6') ? '6' : '0';
+ break;
+ default:
+ adjacent = '0';
+ }
+
+ if ( i == 0 ) {
+ adjacent = code;
+ i++;
+ } else if ( code != '0' ) {
+ if ( i == MAXPHONEMELEN )
+ break;
+ adjacent = phoneme[i] = code;
+ i++;
+ }
+ }
+
+ if ( i > 0 )
+ phoneme[i] = '\0';
+
+ return( slapi_ch_strdup( phoneme ) );
+}
+
+#else
+#if defined(METAPHONE)
+
+/*
+ * Metaphone copied from C Gazette, June/July 1991, pp 56-57,
+ * author Gary A. Parker, with changes by Bernard Tiffany of the
+ * University of Michigan, and more changes by Tim Howes of the
+ * University of Michigan.
+ */
+
+/* Character coding array */
+static char vsvfn[26] = {
+ 1, 16, 4, 16, 9, 2, 4, 16, 9, 2, 0, 2, 2,
+ /* A B C D E F G H I J K L M */
+ 2, 1, 4, 0, 2, 4, 4, 1, 0, 0, 0, 8, 0};
+ /* N O P Q R S T U V W X Y Z */
+
+/* Macros to access character coding array */
+#define vowel(x) ((x) != '\0' && vsvfn[(x) - 'A'] & 1) /* AEIOU */
+#define same(x) ((x) != '\0' && vsvfn[(x) - 'A'] & 2) /* FJLMNR */
+#define varson(x) ((x) != '\0' && vsvfn[(x) - 'A'] & 4) /* CGPST */
+#define frontv(x) ((x) != '\0' && vsvfn[(x) - 'A'] & 8) /* EIY */
+#define noghf(x) ((x) != '\0' && vsvfn[(x) - 'A'] & 16) /* BDH */
+
+char *
+phonetic( char *Word )
+{
+ char *n, *n_start, *n_end; /* pointers to string */
+ char *metaph_end; /* pointers to metaph */
+ char ntrans[42]; /* word with uppercase letters */
+ int KSflag; /* state flag for X -> KS */
+ char buf[MAXPHONEMELEN + 2];
+ char *Metaph;
+
+ /*
+ * Copy Word to internal buffer, dropping non-alphabetic characters
+ * and converting to upper case
+ */
+ n = ntrans + 4; n_end = ntrans + 35;
+ while (!iswordbreak( Word ) && n < n_end) {
+ if (isascii(*Word)) {
+ if (isalpha(*Word)) {
+ *n++ = TOUPPER(*Word);
+ }
+ ++Word;
+ } else {
+ auto const size_t len = LDAP_UTF8COPY(n, Word);
+ n += len; Word += len;
+ }
+ }
+ Metaph = buf;
+ *Metaph = '\0';
+ if (n == ntrans + 4) {
+ return( slapi_ch_strdup( buf ) ); /* Return if null */
+ }
+ n_end = n; /* Set n_end to end of string */
+
+ /* ntrans[0] will always be == 0 */
+ ntrans[0] = '\0';
+ ntrans[1] = '\0';
+ ntrans[2] = '\0';
+ ntrans[3] = '\0';
+ *n++ = 0;
+ *n++ = 0;
+ *n++ = 0;
+ *n = 0; /* Pad with nulls */
+ n = ntrans + 4; /* Assign pointer to start */
+
+ /* Check for PN, KN, GN, AE, WR, WH, and X at start */
+ switch (*n) {
+ case 'P':
+ case 'K':
+ case 'G':
+ /* 'PN', 'KN', 'GN' becomes 'N' */
+ if (*(n + 1) == 'N')
+ *n++ = 0;
+ break;
+ case 'A':
+ /* 'AE' becomes 'E' */
+ if (*(n + 1) == 'E')
+ *n++ = 0;
+ break;
+ case 'W':
+ /* 'WR' becomes 'R', and 'WH' to 'H' */
+ if (*(n + 1) == 'R')
+ *n++ = 0;
+ else if (*(n + 1) == 'H') {
+ *(n + 1) = *n;
+ *n++ = 0;
+ }
+ break;
+ case 'X':
+ /* 'X' becomes 'S' */
+ *n = 'S';
+ break;
+ }
+
+ /*
+ * Now, loop step through string, stopping at end of string or when
+ * the computed 'metaph' is MAXPHONEMELEN characters long
+ */
+
+ KSflag = 0; /* state flag for KS translation */
+ for (metaph_end = Metaph + MAXPHONEMELEN, n_start = n;
+ n <= n_end && Metaph < metaph_end; n++) {
+ if (KSflag) {
+ KSflag = 0;
+ *Metaph++ = 'S';
+ } else if (!isascii(*n)) {
+ *Metaph++ = *n;
+ } else {
+ /* Drop duplicates except for CC */
+ if (*(n - 1) == *n && *n != 'C')
+ continue;
+ /* Check for F J L M N R or first letter vowel */
+ if (same(*n) || (n == n_start && vowel(*n))) {
+ *Metaph++ = *n;
+ } else {
+ switch (*n) {
+ case 'B':
+
+ /*
+ * B unless in -MB
+ */
+ if (n < (n_end - 1) && *(n - 1) != 'M') {
+ *Metaph++ = *n;
+ }
+ break;
+ case 'C':
+
+ /*
+ * X if in -CIA-, -CH- else S if in
+ * -CI-, -CE-, -CY- else dropped if
+ * in -SCI-, -SCE-, -SCY- else K
+ */
+ if (*(n - 1) != 'S' || !frontv(*(n + 1))) {
+ if (*(n + 1) == 'I' && *(n + 2) == 'A') {
+ *Metaph++ = 'X';
+ } else if (frontv(*(n + 1))) {
+ *Metaph++ = 'S';
+ } else if (*(n + 1) == 'H') {
+ *Metaph++ = ((n == n_start && !vowel(*(n + 2)))
+ || *(n - 1) == 'S')
+ ? (char) 'K' : (char) 'X';
+ } else {
+ *Metaph++ = 'K';
+ }
+ }
+ break;
+ case 'D':
+
+ /*
+ * J if in DGE or DGI or DGY else T
+ */
+ *Metaph++ = (*(n + 1) == 'G' && frontv(*(n + 2)))
+ ? (char) 'J' : (char) 'T';
+ break;
+ case 'G':
+
+ /*
+ * F if in -GH and not B--GH, D--GH,
+ * -H--GH, -H---GH else dropped if
+ * -GNED, -GN, -DGE-, -DGI-, -DGY-
+ * else J if in -GE-, -GI-, -GY- and
+ * not GG else K
+ */
+ if ((*(n + 1) != 'J' || vowel(*(n + 2))) &&
+ (*(n + 1) != 'N' || ((n + 1) < n_end &&
+ (*(n + 2) != 'E' || *(n + 3) != 'D'))) &&
+ (*(n - 1) != 'D' || !frontv(*(n + 1))))
+ *Metaph++ = (frontv(*(n + 1)) &&
+ *(n + 2) != 'G') ? (char) 'G' : (char) 'K';
+ else if (*(n + 1) == 'H' && !noghf(*(n - 3)) &&
+ *(n - 4) != 'H')
+ *Metaph++ = 'F';
+ break;
+ case 'H':
+
+ /*
+ * H if before a vowel and not after
+ * C, G, P, S, T else dropped
+ */
+ if (!varson(*(n - 1)) && (!vowel(*(n - 1)) ||
+ vowel(*(n + 1))))
+ *Metaph++ = 'H';
+ break;
+ case 'K':
+
+ /*
+ * dropped if after C else K
+ */
+ if (*(n - 1) != 'C')
+ *Metaph++ = 'K';
+ break;
+ case 'P':
+
+ /*
+ * F if before H, else P
+ */
+ *Metaph++ = *(n + 1) == 'H' ?
+ (char) 'F' : (char) 'P';
+ break;
+ case 'Q':
+
+ /*
+ * K
+ */
+ *Metaph++ = 'K';
+ break;
+ case 'S':
+
+ /*
+ * X in -SH-, -SIO- or -SIA- else S
+ */
+ *Metaph++ = (*(n + 1) == 'H' ||
+ (*(n + 1) == 'I' && (*(n + 2) == 'O' ||
+ *(n + 2) == 'A')))
+ ? (char) 'X' : (char) 'S';
+ break;
+ case 'T':
+
+ /*
+ * X in -TIA- or -TIO- else 0 (zero)
+ * before H else dropped if in -TCH-
+ * else T
+ */
+ if (*(n + 1) == 'I' && (*(n + 2) == 'O' ||
+ *(n + 2) == 'A'))
+ *Metaph++ = 'X';
+ else if (*(n + 1) == 'H')
+ *Metaph++ = '0';
+ else if (*(n + 1) != 'C' || *(n + 2) != 'H')
+ *Metaph++ = 'T';
+ break;
+ case 'V':
+
+ /*
+ * F
+ */
+ *Metaph++ = 'F';
+ break;
+ case 'W':
+
+ /*
+ * W after a vowel, else dropped
+ */
+ case 'Y':
+
+ /*
+ * Y unless followed by a vowel
+ */
+ if (vowel(*(n + 1)))
+ *Metaph++ = *n;
+ break;
+ case 'X':
+
+ /*
+ * KS
+ */
+ if (n == n_start)
+ *Metaph++ = 'S';
+ else {
+ *Metaph++ = 'K'; /* Insert K, then S */
+ KSflag = 1;
+ }
+ break;
+ case 'Z':
+
+ /*
+ * S
+ */
+ *Metaph++ = 'S';
+ break;
+ }
+ }
+ }
+ }
+
+ *Metaph = 0; /* Null terminate */
+ return( slapi_ch_strdup( buf ) );
+}
+
+#endif /* METAPHONE */
+#endif /* !SOUNDEX */
diff --git a/ldap/servers/plugins/syntaxes/sicis.c b/ldap/servers/plugins/syntaxes/sicis.c
new file mode 100644
index 00000000..4bd6623d
--- /dev/null
+++ b/ldap/servers/plugins/syntaxes/sicis.c
@@ -0,0 +1,139 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2002 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+/*
+ * sicis.c - space insensitive string syntax routines.
+ * these strings are also case insensitive.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include "syntax.h"
+
+static int sicis_filter_ava( Slapi_PBlock *pb, struct berval *bvfilter,
+ Slapi_Value **bvals, int ftype, Slapi_Value **retVal );
+static int sicis_filter_sub( Slapi_PBlock *pb, char *initial, char **any,
+ char *final, Slapi_Value **bvals );
+static int sicis_values2keys( Slapi_PBlock *pb, Slapi_Value **val,
+ Slapi_Value ***ivals, int ftype );
+static int sicis_assertion2keys_ava( Slapi_PBlock *pb, Slapi_Value *val,
+ Slapi_Value ***ivals, int ftype );
+static int sicis_assertion2keys_sub( Slapi_PBlock *pb, char *initial,
+ char **any, char *final, Slapi_Value ***ivals );
+static int sicis_compare(struct berval *v1, struct berval *v2);
+
+/* the first name is the official one from RFC 2252 */
+static char *names[] = { "SpaceInsensitiveString",
+ SPACE_INSENSITIVE_STRING_SYNTAX_OID, 0 };
+
+static Slapi_PluginDesc pdesc = { "spaceinsensitivestring-syntax",
+ PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT,
+ "space insensitive string attribute syntax plugin" };
+
+int
+sicis_init( Slapi_PBlock *pb )
+{
+ int rc, flags;
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "=> sicis_init\n", 0, 0, 0 );
+
+ rc = slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION,
+ (void *) SLAPI_PLUGIN_VERSION_01 );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION,
+ (void *)&pdesc );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_FILTER_AVA,
+ (void *) sicis_filter_ava );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_FILTER_SUB,
+ (void *) sicis_filter_sub );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_VALUES2KEYS,
+ (void *) sicis_values2keys );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_AVA,
+ (void *) sicis_assertion2keys_ava );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_SUB,
+ (void *) sicis_assertion2keys_sub );
+ flags = SLAPI_PLUGIN_SYNTAX_FLAG_ORDERING;
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_FLAGS,
+ (void *) &flags );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_NAMES,
+ (void *) names );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_OID,
+ (void *) SPACE_INSENSITIVE_STRING_SYNTAX_OID );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_COMPARE,
+ (void *) sicis_compare );
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "<= sicis_init %d\n", rc, 0, 0 );
+ return( rc );
+}
+
+static int
+sicis_filter_ava(
+ Slapi_PBlock *pb,
+ struct berval *bvfilter,
+ Slapi_Value **bvals,
+ int ftype,
+ Slapi_Value **retVal
+)
+{
+ return( string_filter_ava( bvfilter, bvals, SYNTAX_SI | SYNTAX_CIS,
+ ftype, retVal ) );
+}
+
+
+static int
+sicis_filter_sub(
+ Slapi_PBlock *pb,
+ char *initial,
+ char **any,
+ char *final,
+ Slapi_Value **bvals
+)
+{
+ return( string_filter_sub( pb, initial, any, final, bvals, SYNTAX_SI | SYNTAX_CIS ) );
+}
+
+static int
+sicis_values2keys(
+ Slapi_PBlock *pb,
+ Slapi_Value **vals,
+ Slapi_Value ***ivals,
+ int ftype
+)
+{
+ return( string_values2keys( pb, vals, ivals, SYNTAX_SI | SYNTAX_CIS,
+ ftype ) );
+}
+
+static int
+sicis_assertion2keys_ava(
+ Slapi_PBlock *pb,
+ Slapi_Value *val,
+ Slapi_Value ***ivals,
+ int ftype
+)
+{
+ return(string_assertion2keys_ava( pb, val, ivals,
+ SYNTAX_SI | SYNTAX_CIS, ftype ));
+}
+
+static int
+sicis_assertion2keys_sub(
+ Slapi_PBlock *pb,
+ char *initial,
+ char **any,
+ char *final,
+ Slapi_Value ***ivals
+)
+{
+ return( string_assertion2keys_sub( pb, initial, any, final, ivals,
+ SYNTAX_SI | SYNTAX_CIS ) );
+}
+
+static int sicis_compare(
+ struct berval *v1,
+ struct berval *v2
+)
+{
+ return value_cmp(v1, v2, SYNTAX_SI|SYNTAX_CIS, 3 /* Normalise both values */);
+}
diff --git a/ldap/servers/plugins/syntaxes/string.c b/ldap/servers/plugins/syntaxes/string.c
new file mode 100644
index 00000000..d06a15f5
--- /dev/null
+++ b/ldap/servers/plugins/syntaxes/string.c
@@ -0,0 +1,612 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* string.c - common string syntax routines */
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include "syntax.h"
+#if defined(IRIX)
+#include <unistd.h>
+#endif
+#if defined( MACOS ) || defined( DOS ) || defined( _WIN32 ) || defined( NEED_BSDREGEX )
+#include "regex.h"
+#endif
+
+static int string_filter_approx( struct berval *bvfilter,
+ Slapi_Value **bvals, Slapi_Value **retVal );
+static void substring_comp_keys( Slapi_Value ***ivals, int *nsubs, char *str,
+ int prepost, int syntax );
+
+int
+string_filter_ava( struct berval *bvfilter, Slapi_Value **bvals, int syntax,
+ int ftype, Slapi_Value **retVal )
+{
+ int i, rc;
+ struct berval bvfilter_norm;
+
+ if(retVal) {
+ *retVal = NULL;
+ }
+ if ( ftype == LDAP_FILTER_APPROX ) {
+ return( string_filter_approx( bvfilter, bvals, retVal ) );
+ }
+
+ bvfilter_norm.bv_val = slapi_ch_malloc( bvfilter->bv_len + 1 );
+ SAFEMEMCPY( bvfilter_norm.bv_val, bvfilter->bv_val, bvfilter->bv_len );
+ bvfilter_norm.bv_val[bvfilter->bv_len] = '\0';
+ value_normalize( bvfilter_norm.bv_val, syntax, 1 /* trim leading blanks */ );
+
+ for ( i = 0; bvals[i] != NULL; i++ ) {
+ rc = value_cmp( (struct berval*)slapi_value_get_berval(bvals[i]), &bvfilter_norm, syntax, 1/* Normalise the first value only */ );
+ switch ( ftype ) {
+ case LDAP_FILTER_GE:
+ if ( rc >= 0 ) {
+ if(retVal) {
+ *retVal = bvals[i];
+ }
+ slapi_ch_free ((void**)&bvfilter_norm.bv_val);
+ return( 0 );
+ }
+ break;
+ case LDAP_FILTER_LE:
+ if ( rc <= 0 ) {
+ if(retVal) {
+ *retVal = bvals[i];
+ }
+ slapi_ch_free ((void**)&bvfilter_norm.bv_val);
+ return( 0 );
+ }
+ break;
+ case LDAP_FILTER_EQUALITY:
+ if ( rc == 0 ) {
+ if(retVal) {
+ *retVal = bvals[i];
+ }
+ slapi_ch_free ((void**)&bvfilter_norm.bv_val);
+ return( 0 );
+ }
+ break;
+ }
+ }
+
+ slapi_ch_free ((void**)&bvfilter_norm.bv_val);
+ return( -1 );
+}
+
+static int
+string_filter_approx( struct berval *bvfilter, Slapi_Value **bvals,
+ Slapi_Value **retVal)
+{
+ int i, rc;
+ int ava_wordcount;
+ char *w1, *w2, *c1, *c2;
+
+ /*
+ * try to match words in each filter value in order
+ * in the attribute value.
+ * XXX should do this once for the filter and save it XXX
+ */
+ rc = -1;
+ if(retVal) {
+ *retVal = NULL;
+ }
+ for ( i = 0; bvals[i] != NULL; i++ ) {
+ w2 = (char*)slapi_value_get_string(bvals[i]); /* JCM cast */
+ ava_wordcount = 0;
+ /* for each word in the filter value */
+ for ( w1 = first_word( bvfilter->bv_val ); w1 != NULL;
+ w1 = next_word( w1 ) ) {
+ ++ava_wordcount;
+ if ( (c1 = phonetic( w1 )) == NULL ) {
+ break;
+ }
+
+ /*
+ * for each word in the attribute value from
+ * where we left off...
+ */
+ for ( w2 = first_word( w2 ); w2 != NULL;
+ w2 = next_word( w2 ) ) {
+ c2 = phonetic( w2 );
+ rc = strcmp( c1, c2 );
+ slapi_ch_free((void**)&c2 );
+ if ( rc == 0 ) {
+ if(retVal) {
+ *retVal = bvals[i];
+ }
+ break;
+ }
+ }
+ slapi_ch_free((void**)&c1 );
+
+ /*
+ * if we stopped because we ran out of words
+ * before making a match, go on to the next
+ * value. otherwise try to keep matching
+ * words in this value from where we left off.
+ */
+ if ( w2 == NULL ) {
+ break;
+ } else {
+ w2 = next_word( w2 );
+ }
+ }
+ /*
+ * if we stopped because we ran out of words and
+ * we found at leasy one word, we have a match.
+ */
+ if ( w1 == NULL && ava_wordcount > 0 ) {
+ rc = 0;
+ break;
+ }
+ }
+ LDAPDebug( LDAP_DEBUG_TRACE, "<= string_filter_approx %d\n",
+ rc, 0, 0 );
+
+ return( rc );
+}
+
+int
+string_filter_sub( Slapi_PBlock *pb, char *initial, char **any, char *final,
+ Slapi_Value **bvals, int syntax )
+{
+ int i, j, rc;
+ char *p, *end, *realval, *tmpbuf;
+ size_t tmpbufsize;
+ char pat[BUFSIZ];
+ char buf[BUFSIZ];
+ char ebuf[BUFSIZ];
+
+ LDAPDebug( LDAP_DEBUG_FILTER, "=> string_filter_sub\n",
+ 0, 0, 0 );
+
+ /*
+ * construct a regular expression corresponding to the
+ * filter and let regex do the work for each value
+ * XXX should do this once and save it somewhere XXX
+ */
+ pat[0] = '\0';
+ p = pat;
+ end = pat + sizeof(pat) - 2; /* leave room for null */
+ if ( initial != NULL ) {
+ value_normalize( initial, syntax, 1 /* trim leading blanks */ );
+ strcpy( p, "^" );
+ p = strchr( p, '\0' );
+ /* 2 * in case every char is special */
+ if ( p + 2 * strlen( initial ) > end ) {
+ LDAPDebug( LDAP_DEBUG_ANY, "not enough pattern space\n",
+ 0, 0, 0 );
+ return( -1 );
+ }
+ filter_strcpy_special( p, initial );
+ p = strchr( p, '\0' );
+ }
+ if ( any != NULL ) {
+ for ( i = 0; any[i] != NULL; i++ ) {
+ value_normalize( any[i], syntax, 0 /* DO NOT trim leading blanks */ );
+ /* ".*" + value */
+ if ( p + 2 * strlen( any[i] ) + 2 > end ) {
+ LDAPDebug( LDAP_DEBUG_ANY,
+ "not enough pattern space\n", 0, 0, 0 );
+ return( -1 );
+ }
+ strcpy( p, ".*" );
+ p = strchr( p, '\0' );
+ filter_strcpy_special( p, any[i] );
+ p = strchr( p, '\0' );
+ }
+ }
+ if ( final != NULL ) {
+ value_normalize( final, syntax, 0 /* DO NOT trim leading blanks */ );
+ /* ".*" + value */
+ if ( p + 2 * strlen( final ) + 2 > end ) {
+ LDAPDebug( LDAP_DEBUG_ANY, "not enough pattern space\n",
+ 0, 0, 0 );
+ return( -1 );
+ }
+ strcpy( p, ".*" );
+ p = strchr( p, '\0' );
+ filter_strcpy_special( p, final );
+ p = strchr( p, '\0' );
+ strcpy( p, "$" );
+ }
+
+ /* compile the regex */
+ slapd_re_lock();
+ if ( (p = slapd_re_comp( pat )) != 0 ) {
+ LDAPDebug( LDAP_DEBUG_ANY, "re_comp (%s) failed (%s)\n",
+ pat, p, 0 );
+ slapd_re_unlock();
+ return( -1 );
+ } else {
+ LDAPDebug( LDAP_DEBUG_TRACE, "re_comp (%s)\n",
+ escape_string( pat, ebuf ), 0, 0 );
+ }
+
+ /*
+ * test the regex against each value
+ */
+ rc = -1;
+ tmpbuf = NULL;
+ tmpbufsize = 0;
+ for ( j = 0; bvals[j] != NULL; j++ ) {
+ int tmprc;
+ size_t len;
+ const struct berval *bvp = slapi_value_get_berval(bvals[j]);
+
+ len = bvp->bv_len;
+ if ( len < sizeof(buf) ) {
+ strcpy( buf, bvp->bv_val );
+ realval = buf;
+ } else if ( len < tmpbufsize ) {
+ strcpy( buf, bvp->bv_val );
+ realval = tmpbuf;
+ } else {
+ tmpbuf = (char *) slapi_ch_realloc( tmpbuf, len + 1 );
+ strcpy( tmpbuf, bvp->bv_val );
+ realval = tmpbuf;
+ }
+ value_normalize( realval, syntax, 1 /* trim leading blanks */ );
+
+ tmprc = slapd_re_exec( realval );
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "re_exec (%s) %i\n",
+ escape_string( realval, ebuf ), tmprc, 0 );
+ if ( tmprc != 0 ) {
+ rc = 0;
+ break;
+ }
+ }
+ slapd_re_unlock();
+ if ( tmpbuf != NULL ) {
+ slapi_ch_free((void**)&tmpbuf );
+ }
+
+ LDAPDebug( LDAP_DEBUG_FILTER, "<= string_filter_sub %d\n",
+ rc, 0, 0 );
+ return( rc );
+}
+
+int
+string_values2keys( Slapi_PBlock *pb, Slapi_Value **bvals,
+ Slapi_Value ***ivals, int syntax, int ftype )
+{
+ int nsubs, numbvals, i, n, j;
+ Slapi_Value **nbvals;
+ char *w, *c, *p;
+ char buf[SUBLEN+1];
+
+ switch ( ftype ) {
+ case LDAP_FILTER_EQUALITY:
+ /* allocate a new array for the normalized values */
+ for ( numbvals = 0; bvals[numbvals] != NULL; numbvals++ ) {
+ /* NULL */
+ }
+ nbvals = (Slapi_Value **) slapi_ch_malloc( (numbvals+1) * sizeof(Slapi_Value *));
+
+ for ( i = 0; i < numbvals; i++ )
+ {
+ c = slapi_ch_strdup(slapi_value_get_string(bvals[i]));
+ value_normalize( c, syntax, 1 /* trim leading blanks */ );
+ nbvals[i] = slapi_value_new_string_passin(c);
+ }
+ nbvals[i] = NULL;
+ *ivals = nbvals;
+ break;
+
+ case LDAP_FILTER_APPROX:
+ /* XXX should not do this twice! XXX */
+ /* get an upper bound on the number of ivals */
+ numbvals = 0;
+ for ( i = 0; bvals[i] != NULL; i++ ) {
+ for ( w = first_word( (char*)slapi_value_get_string(bvals[i]) ); w != NULL;
+ w = next_word( w ) ) {
+ numbvals++;
+ }
+ }
+ nbvals = (Slapi_Value **) slapi_ch_malloc( (numbvals + 1) * sizeof(Slapi_Value *) );
+
+ n = 0;
+ for ( i = 0; bvals[i] != NULL; i++ ) {
+ for ( w = first_word( (char*)slapi_value_get_string(bvals[i]) ); w != NULL;
+ w = next_word( w ) ) {
+ if ( (c = phonetic( w )) != NULL ) {
+ nbvals[n] = slapi_value_new_string_passin(c);
+ n++;
+ }
+ }
+ }
+ nbvals[n] = NULL;
+
+ if ( n == 0 ) {
+ slapi_ch_free((void**)ivals );
+ return( 0 );
+ }
+ *ivals = nbvals;
+ break;
+
+ case LDAP_FILTER_SUBSTRINGS:
+ {
+ /* XXX should remove duplicates! XXX */
+ Slapi_Value *bvdup;
+ const struct berval *bvp;
+ nsubs = 0;
+ for ( i = 0; bvals[i] != NULL; i++ ) {
+ /*
+ * Note: this calculation may err on the high side,
+ * because value_normalize(), which is called below
+ * before we actually create the substring keys, may
+ * reduce the length of the value in some cases. For
+ * example, spaces are removed when space insensitive
+ * strings are normalized. But it's okay for nsubs to
+ * be too big. Since the ivals array is NULL terminated,
+ * the only downside is that we allocate more space than
+ * we really need.
+ */
+ nsubs += slapi_value_get_length(bvals[i]) - SUBLEN + 3;
+ }
+ *ivals = (Slapi_Value **) slapi_ch_malloc( (nsubs + 1) * sizeof(Slapi_Value *) );
+
+ buf[SUBLEN] = '\0';
+ n = 0;
+
+ bvdup= slapi_value_new();
+ for ( i = 0; bvals[i] != NULL; i++ )
+ {
+ c = slapi_ch_strdup(slapi_value_get_string(bvals[i]));
+ value_normalize( c, syntax, 1 /* trim leading blanks */ );
+ slapi_value_set_string_passin(bvdup, c);
+
+ bvp = slapi_value_get_berval(bvdup);
+
+ /* leading */
+ if ( bvp->bv_len > SUBLEN - 2 ) {
+ buf[0] = '^';
+ for ( j = 0; j < SUBLEN - 1; j++ ) {
+ buf[j + 1] = bvp->bv_val[j];
+ }
+ (*ivals)[n] = slapi_value_new_string(buf);
+ n++;
+ }
+
+ /* any */
+ for ( p = bvp->bv_val;
+ p < (bvp->bv_val + bvp->bv_len - SUBLEN + 1);
+ p++ ) {
+ for ( j = 0; j < SUBLEN; j++ ) {
+ buf[j] = p[j];
+ }
+ buf[SUBLEN] = '\0';
+ (*ivals)[n] = slapi_value_new_string(buf);
+ n++;
+ }
+
+ /* trailing */
+ if ( bvp->bv_len > SUBLEN - 2 ) {
+ p = bvp->bv_val + bvp->bv_len - SUBLEN + 1;
+ for ( j = 0; j < SUBLEN - 1; j++ ) {
+ buf[j] = p[j];
+ }
+ buf[SUBLEN - 1] = '$';
+ (*ivals)[n] = slapi_value_new_string(buf);
+ n++;
+ }
+ }
+ slapi_value_free(&bvdup);
+ (*ivals)[n] = NULL;
+ }
+ break;
+ }
+
+ return( 0 );
+}
+
+
+/* we've added code to make our equality filter processing faster */
+
+int
+string_assertion2keys_ava(
+ Slapi_PBlock *pb,
+ Slapi_Value *val,
+ Slapi_Value ***ivals,
+ int syntax,
+ int ftype
+)
+{
+ int i, numbvals;
+ size_t len;
+ char *w, *c;
+ Slapi_Value *tmpval=NULL;
+
+ switch ( ftype ) {
+ case LDAP_FILTER_EQUALITY_FAST:
+ /* this code is trying to avoid multiple malloc/frees */
+ len=slapi_value_get_length(val);
+ tmpval=(*ivals)[0];
+ if (len >= tmpval->bv.bv_len) {
+ tmpval->bv.bv_val=(char *)slapi_ch_malloc(len+1);
+ }
+ memcpy(tmpval->bv.bv_val,slapi_value_get_string(val),len);
+ tmpval->bv.bv_val[len]='\0';
+ value_normalize(tmpval->bv.bv_val, syntax, 1 /* trim leading blanks */ );
+ tmpval->bv.bv_len=strlen(tmpval->bv.bv_val);
+ break;
+ case LDAP_FILTER_EQUALITY:
+ (*ivals) = (Slapi_Value **) slapi_ch_malloc( 2 * sizeof(Slapi_Value *) );
+ (*ivals)[0] = slapi_value_dup( val );
+ value_normalize( (*ivals)[0]->bv.bv_val, syntax, 1 /* trim leading blanks */ );
+ (*ivals)[0]->bv.bv_len = strlen( (*ivals)[0]->bv.bv_val );
+ (*ivals)[1] = NULL;
+ break;
+
+ case LDAP_FILTER_APPROX:
+ /* XXX should not do this twice! XXX */
+ /* get an upper bound on the number of ivals */
+ numbvals = 0;
+ for ( w = first_word( (char*)slapi_value_get_string(val) ); w != NULL;
+ w = next_word( w ) ) {
+ numbvals++;
+ }
+ (*ivals) = (Slapi_Value **) slapi_ch_malloc( (numbvals + 1) *
+ sizeof(Slapi_Value *) );
+
+ i = 0;
+ for ( w = first_word( (char*)slapi_value_get_string(val) ); w != NULL;
+ w = next_word( w ) ) {
+ if ( (c = phonetic( w )) != NULL ) {
+ (*ivals)[i] = slapi_value_new_string_passin(c);
+ i++;
+ }
+ }
+ (*ivals)[i] = NULL;
+
+ if ( i == 0 ) {
+ slapi_ch_free((void**)ivals );
+ return( 0 );
+ }
+ break;
+ default:
+ LDAPDebug( LDAP_DEBUG_ANY,
+ "string_assertion2keys_ava: unknown ftype 0x%x\n",
+ ftype, 0, 0 );
+ break;
+ }
+
+ return( 0 );
+}
+
+int
+string_assertion2keys_sub(
+ Slapi_PBlock *pb,
+ char *initial,
+ char **any,
+ char *final,
+ Slapi_Value ***ivals,
+ int syntax
+)
+{
+ int nsubs, i, len;
+
+ *ivals = NULL;
+
+ /*
+ * First figure out how many keys we will return. The answer is based
+ * on the length of each assertion value. Since normalization may
+ * reduce the length (such as when spaces are removed from space
+ * insensitive strings), we call value_normalize() before checking
+ * the length.
+ */
+ nsubs = 0;
+ if ( initial != NULL ) {
+ value_normalize( initial, syntax, 0 /* do not trim leading blanks */ );
+ if ( strlen( initial ) > SUBLEN - 2 ) {
+ nsubs += strlen( initial ) - SUBLEN + 2;
+ } else {
+ initial = NULL; /* save some work later */
+ }
+ }
+ for ( i = 0; any != NULL && any[i] != NULL; i++ ) {
+ value_normalize( any[i], syntax, 0 /* do not trim leading blanks */ );
+ len = strlen( any[i] );
+ if ( len >= SUBLEN ) {
+ nsubs += len - SUBLEN + 1;
+ }
+ }
+ if ( final != NULL ) {
+ value_normalize( final, syntax, 0 /* do not trim leading blanks */ );
+ if ( strlen( final ) > SUBLEN - 2 ) {
+ nsubs += strlen( final ) - SUBLEN + 2;
+ } else {
+ final = NULL; /* save some work later */
+ }
+ }
+ if ( nsubs == 0 ) { /* no keys to return */
+ return( 0 );
+ }
+
+ /*
+ * Next, allocated the ivals array and fill it in with the actual
+ * keys. *ivals is a NULL terminated array of Slapi_Value pointers.
+ */
+
+ *ivals = (Slapi_Value **) slapi_ch_malloc( (nsubs + 1) * sizeof(Slapi_Value *) );
+
+ nsubs = 0;
+ if ( initial != NULL ) {
+ substring_comp_keys( ivals, &nsubs, initial, '^', syntax );
+ }
+ for ( i = 0; any != NULL && any[i] != NULL; i++ ) {
+ if ( strlen( any[i] ) < SUBLEN ) {
+ continue;
+ }
+ substring_comp_keys( ivals, &nsubs, any[i], 0, syntax );
+ }
+ if ( final != NULL ) {
+ substring_comp_keys( ivals, &nsubs, final, '$', syntax );
+ }
+ (*ivals)[nsubs] = NULL;
+
+ return( 0 );
+}
+
+static void
+substring_comp_keys(
+ Slapi_Value ***ivals,
+ int *nsubs,
+ char *str,
+ int prepost,
+ int syntax
+)
+{
+ int i, len;
+ char *p;
+ char buf[SUBLEN + 1];
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "=> substring_comp_keys (%s) %d\n",
+ str, prepost, 0 );
+
+ len = strlen( str );
+
+ /* prepend ^ for initial substring */
+ if ( prepost == '^' )
+ {
+ buf[0] = '^';
+ for ( i = 0; i < SUBLEN - 1; i++ )
+ {
+ buf[i + 1] = str[i];
+ }
+ buf[SUBLEN] = '\0';
+ (*ivals)[*nsubs] = slapi_value_new_string(buf);
+ (*nsubs)++;
+ }
+
+ for ( p = str; p < (str + len - SUBLEN + 1); p++ )
+ {
+ for ( i = 0; i < SUBLEN; i++ )
+ {
+ buf[i] = p[i];
+ }
+ buf[SUBLEN] = '\0';
+ (*ivals)[*nsubs] = slapi_value_new_string(buf);
+ (*nsubs)++;
+ }
+
+ if ( prepost == '$' )
+ {
+ p = str + len - SUBLEN + 1;
+ for ( i = 0; i < SUBLEN - 1; i++ )
+ {
+ buf[i] = p[i];
+ }
+ buf[SUBLEN - 1] = '$';
+ buf[SUBLEN] = '\0';
+ (*ivals)[*nsubs] = slapi_value_new_string(buf);
+ (*nsubs)++;
+ }
+
+ LDAPDebug( LDAP_DEBUG_TRACE, "<= substring_comp_keys\n", 0, 0, 0 );
+}
diff --git a/ldap/servers/plugins/syntaxes/syntax.h b/ldap/servers/plugins/syntaxes/syntax.h
new file mode 100644
index 00000000..d6d883c9
--- /dev/null
+++ b/ldap/servers/plugins/syntaxes/syntax.h
@@ -0,0 +1,42 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* syntax.h - string syntax definitions */
+
+#ifndef _LIBSYNTAX_H_
+#define _LIBSYNTAX_H_
+
+#define SLAPD_LOGGING 1
+
+#include "slap.h"
+#include "slapi-plugin.h"
+#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */
+
+#define SYNTAX_CIS 1
+#define SYNTAX_CES 2
+#define SYNTAX_TEL 4 /* telephone number: used with SYNTAX_CIS */
+#define SYNTAX_DN 8 /* distinguished name: used with SYNTAX_CIS */
+#define SYNTAX_SI 16 /* space insensitive: used with SYNTAX_CIS */
+
+#define SUBLEN 3
+
+#ifndef MIN
+#define MIN( a, b ) (a < b ? a : b )
+#endif
+
+int string_filter_sub( Slapi_PBlock *pb, char *initial, char **any, char *final,Slapi_Value **bvals, int syntax );
+int string_filter_ava( struct berval *bvfilter, Slapi_Value **bvals, int syntax,int ftype, Slapi_Value **retVal );
+int string_values2keys( Slapi_PBlock *pb, Slapi_Value **bvals,Slapi_Value ***ivals, int syntax, int ftype );
+int string_assertion2keys_ava(Slapi_PBlock *pb,Slapi_Value *val,Slapi_Value ***ivals,int syntax,int ftype );
+int string_assertion2keys_sub(Slapi_PBlock *pb,char *initial,char **any,char *final,Slapi_Value ***ivals,int syntax);
+int value_cmp(struct berval *v1,struct berval *v2,int syntax,int normalize);
+void value_normalize(char *s,int syntax,int trim_leading_blanks);
+
+char *first_word( char *s );
+char *next_word( char *s );
+char *phonetic( char *s );
+
+
+#endif
diff --git a/ldap/servers/plugins/syntaxes/tel.c b/ldap/servers/plugins/syntaxes/tel.c
new file mode 100644
index 00000000..c2c80d1a
--- /dev/null
+++ b/ldap/servers/plugins/syntaxes/tel.c
@@ -0,0 +1,135 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* tel.c - telephonenumber syntax routines */
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include "syntax.h"
+
+static int tel_filter_ava( Slapi_PBlock *pb, struct berval *bvfilter,
+ Slapi_Value **bvals, int ftype, Slapi_Value **retVal );
+static int tel_filter_sub( Slapi_PBlock *pb, char *initial, char **any,
+ char *final, Slapi_Value **bvals );
+static int tel_values2keys( Slapi_PBlock *pb, Slapi_Value **val,
+ Slapi_Value ***ivals, int ftype );
+static int tel_assertion2keys_ava( Slapi_PBlock *pb, Slapi_Value *val,
+ Slapi_Value ***ivals, int ftype );
+static int tel_assertion2keys_sub( Slapi_PBlock *pb, char *initial, char **any,
+ char *final, Slapi_Value ***ivals );
+static int tel_compare(struct berval *v1, struct berval *v2);
+
+/* the first name is the official one from RFC 2252 */
+static char *names[] = { "TelephoneNumber", "tel", TELEPHONE_SYNTAX_OID, 0 };
+
+static Slapi_PluginDesc pdesc = { "tele-syntax", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT,
+ "telephoneNumber attribute syntax plugin" };
+
+int
+tel_init( Slapi_PBlock *pb )
+{
+ int rc, flags;
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "=> tel_init\n", 0, 0, 0 );
+
+ rc = slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION,
+ (void *) SLAPI_PLUGIN_VERSION_01 );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION,
+ (void *)&pdesc );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_FILTER_AVA,
+ (void *) tel_filter_ava );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_FILTER_SUB,
+ (void *) tel_filter_sub );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_VALUES2KEYS,
+ (void *) tel_values2keys );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_AVA,
+ (void *) tel_assertion2keys_ava );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_SUB,
+ (void *) tel_assertion2keys_sub );
+ flags = SLAPI_PLUGIN_SYNTAX_FLAG_ORDERING;
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_FLAGS,
+ (void *) &flags );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_NAMES,
+ (void *) names );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_OID,
+ (void *) TELEPHONE_SYNTAX_OID );
+ rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_SYNTAX_COMPARE,
+ (void *) tel_compare );
+
+ LDAPDebug( LDAP_DEBUG_PLUGIN, "<= tel_init %d\n", rc, 0, 0 );
+ return( rc );
+}
+
+static int
+tel_filter_ava(
+ Slapi_PBlock *pb,
+ struct berval *bvfilter,
+ Slapi_Value **bvals,
+ int ftype,
+ Slapi_Value **retVal
+)
+{
+ return( string_filter_ava( bvfilter, bvals, SYNTAX_TEL | SYNTAX_CIS,
+ ftype, retVal ) );
+}
+
+
+static int
+tel_filter_sub(
+ Slapi_PBlock *pb,
+ char *initial,
+ char **any,
+ char *final,
+ Slapi_Value **bvals
+)
+{
+ return( string_filter_sub( pb, initial, any, final, bvals, SYNTAX_TEL | SYNTAX_CIS ) );
+}
+
+static int
+tel_values2keys(
+ Slapi_PBlock *pb,
+ Slapi_Value **vals,
+ Slapi_Value ***ivals,
+ int ftype
+)
+{
+ return( string_values2keys( pb, vals, ivals, SYNTAX_TEL | SYNTAX_CIS,
+ ftype ) );
+}
+
+static int
+tel_assertion2keys_ava(
+ Slapi_PBlock *pb,
+ Slapi_Value *val,
+ Slapi_Value ***ivals,
+ int ftype
+)
+{
+ return(string_assertion2keys_ava( pb, val, ivals,
+ SYNTAX_TEL | SYNTAX_CIS, ftype ));
+}
+
+static int
+tel_assertion2keys_sub(
+ Slapi_PBlock *pb,
+ char *initial,
+ char **any,
+ char *final,
+ Slapi_Value ***ivals
+)
+{
+ return( string_assertion2keys_sub( pb, initial, any, final, ivals,
+ SYNTAX_TEL | SYNTAX_CIS ) );
+}
+
+static int tel_compare(
+ struct berval *v1,
+ struct berval *v2
+)
+{
+ return value_cmp(v1, v2, SYNTAX_TEL|SYNTAX_CIS, 3 /* Normalise both values */);
+}
diff --git a/ldap/servers/plugins/syntaxes/value.c b/ldap/servers/plugins/syntaxes/value.c
new file mode 100644
index 00000000..be496091
--- /dev/null
+++ b/ldap/servers/plugins/syntaxes/value.c
@@ -0,0 +1,209 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* value.c - routines for dealing with values */
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include "syntax.h"
+
+/*
+ * Do not use the SDK ldap_utf8isspace directly until it is faster
+ * than this one.
+ */
+static int
+utf8isspace_fast( char* s )
+{
+ register unsigned char c = *(unsigned char*)s;
+ if (0x80 & c) return(ldap_utf8isspace(s));
+ switch (c) {
+ case 0x09:
+ case 0x0A:
+ case 0x0B:
+ case 0x0C:
+ case 0x0D:
+ case 0x20:
+ return 1;
+ default: break;
+ }
+ return 0;
+}
+
+/*
+** This function is used to normalizes search filter components,
+** and attribute values.
+**
+** jcm: I added the trim_spaces flag since this function
+** was incorrectly modifying search filter components. A search
+** of the form "cn=a* b*" (note the space) would be wrongly
+** normalized into "cn=a*b*", because this function is called
+** once for "a" and once for " b".
+*/
+void
+value_normalize(
+ char *s,
+ int syntax,
+ int trim_spaces
+)
+{
+ char *d;
+ int prevspace, curspace;
+
+ if ( ! (syntax & SYNTAX_CIS) && ! (syntax & SYNTAX_CES) ) {
+ return;
+ }
+
+ if ( syntax & SYNTAX_DN ) {
+ (void) slapi_dn_normalize_case( s );
+ return;
+ }
+
+ d = s;
+ if (trim_spaces) {
+ /* strip leading blanks */
+ while (utf8isspace_fast(s)) {
+ LDAP_UTF8INC(s);
+ }
+ }
+ /* handle value of all spaces - turn into single space */
+ /* unless space insensitive syntax - turn into zero length string */
+ if ( *s == '\0' && s != d ) {
+ if ( ! (syntax & SYNTAX_SI)) {
+ *d++ = ' ';
+ }
+ *d = '\0';
+ return;
+ }
+ prevspace = 0;
+ while ( *s ) {
+ curspace = utf8isspace_fast(s);
+
+ /* ignore spaces and '-' in telephone numbers */
+ if ( (syntax & SYNTAX_TEL) && (curspace || *s == '-') ) {
+ LDAP_UTF8INC(s);
+ continue;
+ }
+
+ /* ignore all spaces if this is a space insensitive value */
+ if ( (syntax & SYNTAX_SI) && curspace ) {
+ LDAP_UTF8INC(s);
+ continue;
+ }
+
+ /* compress multiple blanks */
+ if ( prevspace && curspace ) {
+ LDAP_UTF8INC(s);
+ continue;
+ }
+ prevspace = curspace;
+ if ( syntax & SYNTAX_CIS ) {
+ int ssz, dsz;
+ slapi_utf8ToLower((unsigned char*)s, (unsigned char *)d, &ssz, &dsz);
+ s += ssz;
+ d += dsz;
+ } else {
+ char *np;
+ int sz;
+
+ np = ldap_utf8next(s);
+ if (np == NULL || np == s) break;
+ sz = np - s;
+ memcpy(d,s,sz);
+ d += sz;
+ s += sz;
+ }
+ }
+ *d = '\0';
+ /* strip trailing blanks */
+ if (prevspace && trim_spaces) {
+ char *nd;
+
+ nd = ldap_utf8prev(d);
+ while (nd && utf8isspace_fast(nd)) {
+ d = nd;
+ nd = ldap_utf8prev(d);
+ *d = '\0';
+ }
+ }
+}
+
+int
+value_cmp(
+ struct berval *v1,
+ struct berval *v2,
+ int syntax,
+ int normalize
+)
+{
+ int rc;
+ struct berval bvcopy1;
+ struct berval bvcopy2;
+ char little_buffer[64];
+ size_t buffer_space = sizeof(little_buffer);
+ int buffer_offset = 0;
+ int free_v1 = 0;
+ int free_v2 = 0;
+
+ /* This code used to call malloc up to four times in the copying
+ * of attributes to be normalized. Now we attempt to keep everything
+ * on the stack and only malloc if the data is big
+ */
+ if ( normalize & 1 ) {
+ /* Do we have space in the little buffer ? */
+ if (v1->bv_len < buffer_space) {
+ bvcopy1.bv_len = v1->bv_len;
+ SAFEMEMCPY(&little_buffer[buffer_offset],v1->bv_val,v1->bv_len);
+ bvcopy1.bv_val = &little_buffer[buffer_offset];
+ bvcopy1.bv_val[v1->bv_len] = '\0';
+ v1 = &bvcopy1;
+ buffer_space-= v1->bv_len+1;
+ buffer_offset+= v1->bv_len+1;
+ } else {
+ v1 = ber_bvdup( v1 );
+ free_v1 = 1;
+ }
+ value_normalize( v1->bv_val, syntax, 1 /* trim leading blanks */ );
+ }
+ if ( normalize & 2 ) {
+ /* Do we have space in the little buffer ? */
+ if (v2->bv_len < buffer_space) {
+ bvcopy2.bv_len = v2->bv_len;
+ SAFEMEMCPY(&little_buffer[buffer_offset],v2->bv_val,v2->bv_len);
+ bvcopy2.bv_val = &little_buffer[buffer_offset];
+ bvcopy2.bv_val[v2->bv_len] = '\0';
+ v2 = &bvcopy2;
+ buffer_space-= v2->bv_len+1;
+ buffer_offset+= v2->bv_len+1;
+ } else {
+ v2 = ber_bvdup( v2 );
+ free_v2 = 1;
+ }
+ value_normalize( v2->bv_val, syntax, 1 /* trim leading blanks */ );
+ }
+
+ switch ( syntax ) {
+ case SYNTAX_CIS:
+ case (SYNTAX_CIS | SYNTAX_TEL):
+ case (SYNTAX_CIS | SYNTAX_DN):
+ case (SYNTAX_CIS | SYNTAX_SI):
+ rc = slapi_utf8casecmp( (unsigned char *)v1->bv_val,
+ (unsigned char *)v2->bv_val );
+ break;
+
+ case SYNTAX_CES:
+ rc = strcmp( v1->bv_val, v2->bv_val );
+ break;
+ }
+
+ if ( (normalize & 1) && free_v1) {
+ ber_bvfree( v1 );
+ }
+ if ( (normalize & 2) && free_v2) {
+ ber_bvfree( v2 );
+ }
+
+ return( rc );
+}
diff --git a/ldap/servers/plugins/uiduniq/7bit.c b/ldap/servers/plugins/uiduniq/7bit.c
new file mode 100644
index 00000000..535dbe9c
--- /dev/null
+++ b/ldap/servers/plugins/uiduniq/7bit.c
@@ -0,0 +1,722 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/*
+ * 7bit.c
+ *
+ * Implements a directory server pre-operation plugin to test
+ * attributes for 7 bit clean within a defined subtree in the
+ * directory.
+ *
+ */
+#include <stdio.h>
+#include <slapi-plugin.h>
+#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */
+#include <string.h>
+#include "dirver.h"
+
+/* DBDB this should be pulled from a common header file */
+#ifdef _WIN32
+#ifndef strcasecmp
+#define strcasecmp(x,y) strcmpi(x,y)
+#endif
+#endif
+
+#if defined( LDAP_DEBUG ) && !defined( DEBUG )
+#define DEBUG
+#endif
+
+/*
+ * ISSUES:
+ * How should this plugin handle ACL issues? It seems wrong to reject
+ * adds and modifies because there is already a conflicting UID, when
+ * the request would have failed because of an ACL check anyway.
+ *
+ * This code currently defines a maximum filter string size of 512. Is
+ * this large enough?
+ *
+ * This code currently does not quote the value portion of the filter as
+ * it is created. This is a bug.
+ */
+
+/* */
+#define BEGIN do {
+#define END } while(0);
+
+/*
+ * Slapi plugin descriptor
+ */
+static char *plugin_name = "NS7bitAttr";
+static Slapi_PluginDesc
+pluginDesc = { "NS7bitAttr", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT,
+ "Enforce 7-bit clean attribute values" };
+
+
+/*
+ * More information about constraint failure
+ */
+static char *moreInfo =
+ "The value is not 7-bit clean: ";
+
+/* ------------------------------------------------------------ */
+/*
+ * op_error - Record (and report) an operational error.
+ */
+static int
+op_error(int internal_error)
+{
+ slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
+ "Internal error: %d\n", internal_error);
+
+ return LDAP_OPERATIONS_ERROR;
+}
+
+static void
+issue_error(Slapi_PBlock *pb, int result, char *type, char *value)
+{
+ char *moreinfop;
+ int sz;
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
+ "%s result %d\n", type, result);
+
+ if (value == NULL) {
+ value = "unknown";
+ }
+ sz = strlen(moreInfo) + strlen(value) + 1;
+ moreinfop = (char *)slapi_ch_malloc(sz);
+ sprintf(moreinfop, "%s%s", moreInfo, value);
+
+ /* Send failure to the client */
+ slapi_send_ldap_result(pb, result, 0, moreinfop, 0, 0);
+ slapi_ch_free((void **)&moreinfop);
+
+ return;
+}
+
+
+/*
+ * Check 'value' for 7-bit cleanliness.
+ */
+static int
+bit_check_one_berval(const struct berval *value, char **violated)
+{
+ int result;
+ char *ch;
+ int i;
+
+#ifdef DEBUG
+ slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, "7-bit checking begin\n");
+#endif
+
+ result = LDAP_SUCCESS;
+ /* If no value, can't possibly be a conflict */
+ if ( (struct berval *)NULL == value )
+ return result;
+
+ for(i=0, ch=value->bv_val; ch && i < (int)(value->bv_len) ;
+ ch++, i++)
+ {
+
+ if (( 0x80 & *ch ) != 0 )
+ {
+ result = LDAP_CONSTRAINT_VIOLATION;
+ *violated = value->bv_val;
+ break;
+ }
+ }
+
+ return result;
+}
+
+
+/*
+ * Check a set of values for 7-bit cleanliness.
+ *
+ * If 'attr' is NULL, the values are taken from 'values'.
+ * If 'attr' is non-NULL, the values are taken from 'attr'.
+ */
+static int
+bit_check(Slapi_Attr *attr, struct berval **values, char **violated)
+{
+ int result = LDAP_SUCCESS;
+ *violated = NULL;
+
+ /* If no values, can't possibly be a conflict */
+ if ( (Slapi_Attr *)NULL == attr && (struct berval **)NULL == values )
+ return result;
+
+ if ( (Slapi_Attr *)NULL != attr )
+ {
+ Slapi_Value *v = NULL;
+ int vhint = -1;
+
+ for ( vhint = slapi_attr_first_value( attr, &v );
+ vhint != -1 && LDAP_SUCCESS == result;
+ vhint = slapi_attr_next_value( attr, vhint, &v ))
+ {
+ result = bit_check_one_berval(slapi_value_get_berval(v), violated);
+ }
+ }
+ else
+ {
+ for (;*values != NULL && LDAP_SUCCESS == result; values++)
+ {
+ result = bit_check_one_berval(*values, violated);
+ }
+ }
+
+#ifdef DEBUG
+ slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
+ "7 bit check result = %d\n", result);
+#endif
+
+ return result;
+}
+
+
+/* ------------------------------------------------------------ */
+/*
+ * preop_add - pre-operation plug-in for add
+ */
+static int
+preop_add(Slapi_PBlock *pb)
+{
+ int result;
+ char *violated;
+
+#ifdef DEBUG
+ slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, "ADD begin\n");
+#endif
+
+ result = LDAP_SUCCESS;
+
+ /*
+ * Do constraint check on the added entry. Set result.
+ */
+ BEGIN
+ int err;
+ int argc;
+ char **argv;
+ char **attrName;
+ char *dn;
+ Slapi_Entry *e;
+ Slapi_Attr *attr;
+ char **firstSubtree;
+ char **subtreeDN;
+ int subtreeCnt;
+ int is_replicated_operation;
+
+ /*
+ * Get the arguments
+ */
+ err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGC, &argc);
+ if (err) { result = op_error(53); break; }
+
+ err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGV, &argv);
+ if (err) { result = op_error(54); break; }
+
+ /*
+ * If this is a replication update, just be a noop.
+ */
+ err = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_replicated_operation);
+ if (err) { result = op_error(56); break; }
+ if (is_replicated_operation)
+ {
+ break;
+ }
+
+ /*
+ * Get the target DN for this add operation
+ */
+ err = slapi_pblock_get(pb, SLAPI_ADD_TARGET, &dn);
+ if (err) { result = op_error(50); break; }
+
+#ifdef DEBUG
+ slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, "ADD target=%s\n", dn);
+#endif
+
+ /*
+ * Get the entry data for this add. Check whether it
+ * contains a value for the unique attribute
+ */
+ err = slapi_pblock_get(pb, SLAPI_ADD_ENTRY, &e);
+ if (err) { result = op_error(51); break; }
+
+ for ( firstSubtree = argv; strcmp(*firstSubtree, ",") != 0;
+ firstSubtree++, argc--) {}
+ firstSubtree++;
+ argc--;
+
+ for (attrName = argv; strcmp(*attrName, ",") != 0; attrName++ )
+ {
+ /*
+ * if the attribute is userpassword, check unhashed#user#password
+ * instead. "userpassword" is encoded; it will always pass the 7bit
+ * check.
+ */
+ char *attr_name;
+ if ( strcasecmp(*attrName, "userpassword") == 0 )
+ {
+ attr_name = "unhashed#user#password";
+ } else {
+ attr_name = *attrName;
+ }
+ err = slapi_entry_attr_find(e, attr_name, &attr);
+ if (err) continue; /* break;*/ /* no 7-bit attribute */
+
+ /*
+ * For each DN in the managed list, do 7-bit checking if
+ * the target DN is a subnode in the tree.
+ */
+ for( subtreeDN=firstSubtree, subtreeCnt=argc ;subtreeCnt > 0;
+ subtreeCnt--,subtreeDN++)
+ {
+ /*
+ * issuffix determines whether the target is under the
+ * subtree *subtreeDN
+ */
+ if (slapi_dn_issuffix(dn, *subtreeDN))
+ {
+#ifdef DEBUG
+ slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
+ "ADD subtree=%s\n", *subtreeDN);
+#endif
+
+ /*
+ * Check if the value is 7-bit clean
+ */
+ result = bit_check(attr, NULL, &violated);
+ if (result) break;
+ }
+ }
+ /* don't have to go on if there is a value not 7-bit clean */
+ if (result) break;
+ }
+ END
+
+ if (result) {
+ issue_error(pb, result, "ADD", violated);
+ }
+
+ return (result==LDAP_SUCCESS)?0:-1;
+}
+
+static void
+addMod(LDAPMod ***modary, int *capacity, int *nmods, LDAPMod *toadd)
+{
+ if (*nmods == *capacity) {
+ *capacity += 4;
+ if (*modary) {
+ *modary = (LDAPMod **)slapi_ch_realloc((char *)*modary, *capacity * sizeof(LDAPMod *));
+ } else {
+ *modary = (LDAPMod **)slapi_ch_malloc(*capacity * sizeof(LDAPMod *));
+ }
+ }
+ *modary[*nmods] = toadd;
+ (*nmods)++;
+}
+
+/* ------------------------------------------------------------ */
+/*
+ * preop_modify - pre-operation plug-in for modify
+ */
+static int
+preop_modify(Slapi_PBlock *pb)
+{
+ int result;
+ char *violated;
+ LDAPMod **checkmods = NULL; /* holds mods to check */
+ int checkmodsCapacity = 0; /* max capacity of checkmods */
+
+#ifdef DEBUG
+ slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
+ "MODIFY begin\n");
+#endif
+
+ result = LDAP_SUCCESS;
+
+ BEGIN
+ int err;
+ int argc;
+ char **argv;
+ char **attrName;
+ LDAPMod **mods;
+ LDAPMod **firstMods;
+ LDAPMod *mod;
+ char *target;
+ char **firstSubtree;
+ char **subtreeDN;
+ int subtreeCnt;
+ int is_replicated_operation;
+
+ /*
+ * Get the arguments
+ */
+ err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGC, &argc);
+ if (err) { result = op_error(13); break; }
+
+ err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGV, &argv);
+ if (err) { result = op_error(14); break; }
+
+ /*
+ * If this is a replication update, just be a noop.
+ */
+ err = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_replicated_operation);
+ if (err) { result = op_error(16); break; }
+ if (is_replicated_operation)
+ {
+ break;
+ }
+
+ err = slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &firstMods);
+ if (err) { result = op_error(10); break; }
+
+ /* Get the target DN */
+ err = slapi_pblock_get(pb, SLAPI_MODIFY_TARGET, &target);
+ if (err) { result = op_error(11); break; }
+
+ /*
+ * Look for managed trees that include the target
+ * Arguments before "," are the 7-bit clean attribute names. Arguemnts
+ * after "," are subtreeDN's.
+ */
+ for ( firstSubtree = argv; strcmp(*firstSubtree, ",") != 0;
+ firstSubtree++, argc--) {}
+ firstSubtree++;
+ argc--;
+
+ for (attrName = argv; strcmp(*attrName, ",") != 0; attrName++ )
+ {
+ int modcount = 0;
+ int ii = 0;
+
+ /*
+ * if the attribute is userpassword, check unhashed#user#password
+ * instead. "userpassword" is encoded; it will always pass the 7bit
+ * check.
+ */
+ char *attr_name;
+ if ( strcasecmp(*attrName, "userpassword") == 0 )
+ {
+ attr_name = "unhashed#user#password";
+ } else {
+ attr_name = *attrName;
+ }
+
+ /* There may be more than one mod that matches e.g.
+ changetype: modify
+ delete: uid
+ uid: balster1950
+ -
+ add: uid
+ uid: scottg
+
+ So, we need to first find all mods that contain the attribute
+ which are add or replace ops and are bvalue encoded
+ */
+ /* find out how many mods meet this criteria */
+ for(mods=firstMods;*mods;mods++)
+ {
+ mod = *mods;
+ if ((slapi_attr_type_cmp(mod->mod_type, attr_name, 1) == 0) && /* mod contains target attr */
+ (mod->mod_op & LDAP_MOD_BVALUES) && /* mod is bval encoded (not string val) */
+ (mod->mod_bvalues && mod->mod_bvalues[0]) && /* mod actually contains some values */
+ (((mod->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_ADD) || /* mod is add */
+ (mod->mod_op & LDAP_MOD_REPLACE))) /* mod is replace */
+ {
+ addMod(&checkmods, &checkmodsCapacity, &modcount, mod);
+ }
+ }
+ if (modcount == 0) {
+ continue; /* no mods to check, go to next attr */
+ }
+
+ /*
+ * stop checking at first mod that fails the check
+ */
+ for (ii = 0; (result == 0) && (ii < modcount); ++ii)
+ {
+ mod = checkmods[ii];
+ /*
+ * For each DN in the managed list, do 7-bit checking if
+ * the target DN is a subnode in the tree.
+ */
+ for( subtreeDN=firstSubtree, subtreeCnt=argc ;subtreeCnt > 0;
+ subtreeCnt--,subtreeDN++)
+ {
+ /*
+ * issuffix determines whether the target is under the
+ * subtree *subtreeDN
+ */
+ if (slapi_dn_issuffix(target, *subtreeDN))
+ {
+#ifdef DEBUG
+ slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
+ "MODIFY subtree=%s\n", *subtreeDN);
+#endif
+ /*
+ * Check if the value is 7-bit clean
+ */
+ result = bit_check(NULL, mod->mod_bvalues, &violated);
+ if (result) break;
+ }
+ }
+ }
+ /* don't have to go on if there is a value not 7-bit clean */
+ if (result) break;
+ }
+ END
+
+ slapi_ch_free((void **)&checkmods);
+ if (result) {
+ issue_error(pb, result, "MODIFY", violated);
+ }
+
+ return (result==LDAP_SUCCESS)?0:-1;
+}
+
+/* ------------------------------------------------------------ */
+/*
+ * preop_modrdn - Pre-operation call for modify RDN
+ *
+ * Check that the new RDN does not include attributes that
+ * cause a constraint violation
+ */
+static int
+preop_modrdn(Slapi_PBlock *pb)
+{
+ int result;
+ Slapi_Entry *e;
+ char *violated;
+
+#ifdef DEBUG
+ slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
+ "MODRDN begin\n");
+#endif
+
+ /* Init */
+ result = LDAP_SUCCESS;
+ e = 0;
+
+ BEGIN
+ int err;
+ int argc;
+ char **argv;
+ char **attrName;
+ char *target;
+ char *superior;
+ char *rdn;
+ Slapi_Attr *attr;
+ char **firstSubtree;
+ char **subtreeDN;
+ int subtreeCnt;
+ int is_replicated_operation;
+
+ /*
+ * Get the arguments
+ */
+ err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGC, &argc);
+ if (err) { result = op_error(30); break; }
+
+ err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGV, &argv);
+ if (err) { result = op_error(31); break; }
+
+ /*
+ * If this is a replication update, just be a noop.
+ */
+ err = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_replicated_operation);
+ if (err) { result = op_error(16); break; }
+ if (is_replicated_operation)
+ {
+ break;
+ }
+
+ /* Get the DN of the entry being renamed */
+ err = slapi_pblock_get(pb, SLAPI_MODRDN_TARGET, &target);
+ if (err) { result = op_error(22); break; }
+
+ /* Get superior value - unimplemented in 3.0 DS */
+ err = slapi_pblock_get(pb, SLAPI_MODRDN_NEWSUPERIOR, &superior);
+ if (err) { result = op_error(20); break; }
+
+ /*
+ * No superior means the entry is just renamed at
+ * its current level in the tree. Use the target DN for
+ * determining which managed tree this belongs to
+ */
+ if (!superior) superior = target;
+
+ /* Get the new RDN - this has the attribute values */
+ err = slapi_pblock_get(pb, SLAPI_MODRDN_NEWRDN, &rdn);
+ if (err) { result = op_error(33); break; }
+
+#ifdef DEBUG
+ slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
+ "MODRDN newrdn=%s\n", rdn);
+#endif
+
+ /*
+ * Parse the RDN into attributes by creating a "dummy" entry
+ * and setting the attributes from the RDN.
+ *
+ * The new entry must be freed.
+ */
+ e = slapi_entry_alloc();
+ if (!e) { result = op_error(32); break; }
+
+ /* NOTE: strdup on the rdn, since it will be freed when
+ * the entry is freed */
+
+ slapi_entry_set_dn(e, slapi_ch_strdup(rdn));
+
+ err = slapi_entry_add_rdn_values(e);
+ if (err)
+ {
+ slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
+ "MODRDN bad rdn value=%s\n", rdn);
+ break; /* Bad DN */
+ }
+
+ /*
+ * arguments before "," are the 7-bit clean attribute names. Arguemnts
+ * after "," are subtreeDN's.
+ */
+ for ( firstSubtree = argv; strcmp(*firstSubtree, ",") != 0;
+ firstSubtree++, argc--) {}
+ firstSubtree++;
+ argc--;
+
+ /*
+ * Find out if the node is being moved into one of
+ * the managed subtrees
+ */
+ for (attrName = argv; strcmp(*attrName, ",") != 0; attrName++ )
+ {
+ /*
+ * If the attribut type is userpassword, do not replace it by
+ * unhashed#user#password because unhashed#user#password does not exist
+ * in this case.
+ */
+ /*
+ * Find any 7-bit attribute data in the new RDN
+ */
+ err = slapi_entry_attr_find(e, *attrName, &attr);
+ if (err) continue; /* break;*/ /* no 7-bit attribute */
+
+ /*
+ * For each DN in the managed list, do 7-bit checking if
+ * the target DN is a subnode in the tree.
+ */
+ for( subtreeDN=firstSubtree, subtreeCnt=argc ;subtreeCnt > 0;
+ subtreeCnt--,subtreeDN++)
+ {
+ /*
+ * issuffix determines whether the target is under the
+ * subtree *subtreeDN
+ */
+ if (slapi_dn_issuffix(superior, *subtreeDN))
+ {
+#ifdef DEBUG
+ slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
+ "MODRDN subtree=%s\n", *subtreeDN);
+#endif
+
+ /*
+ * Check if the value is 7-bit clean
+ */
+ result = bit_check(attr, NULL, &violated);
+ if (result) break;
+ }
+ }
+ /* don't have to go on if there is a value not 7-bit clean */
+ if (result) break;
+ }
+ END
+
+ /* Clean-up */
+ if (e) slapi_entry_free(e);
+
+ if (result) {
+ issue_error(pb, result, "MODRDN", violated);
+ }
+
+ return (result==LDAP_SUCCESS)?0:-1;
+}
+
+/* ------------------------------------------------------------ */
+/*
+ * Initialize the plugin
+ *
+ */
+int
+NS7bitAttr_Init(Slapi_PBlock *pb)
+{
+ int err = 0;
+
+ BEGIN
+ int err;
+ int argc;
+ char **argv;
+
+ /* Declare plugin version */
+ err = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION,
+ SLAPI_PLUGIN_VERSION_01);
+ if (err) break;
+
+ /*
+ * Get and normalize arguments
+ */
+ err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGC, &argc);
+ if (err) break;
+
+ err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGV, &argv);
+ if (err) break;
+
+ /*
+ * Arguments before "," are the 7-bit attribute names. Arguments after
+ * "," are the subtree DN's.
+ */
+ if (argc < 1) { err = -1; break; }
+ for(;strcmp(*argv, ",") != 0 && argc > 0; argc--, argv++)
+ {};
+ if (argc == 0) { err = -1; break; }
+ argv++; argc--;
+
+ for(;argc > 0;argc--, argv++)
+ slapi_dn_normalize_case(*argv);
+
+ /* Provide descriptive information */
+ err = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION,
+ (void*)&pluginDesc);
+ if (err) break;
+
+ /* Register functions */
+ err = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_ADD_FN,
+ (void*)preop_add);
+ if (err) break;
+
+ err = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_MODIFY_FN,
+ (void*)preop_modify);
+ if (err) break;
+
+ err = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_MODRDN_FN,
+ (void*)preop_modrdn);
+ if (err) break;
+
+ END
+
+ if (err) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, "NS7bitAttr_Init",
+ "Error: %d\n", err);
+ err = -1;
+ }
+ else
+ slapi_log_error(SLAPI_LOG_PLUGIN, "NS7bitAttr_Init",
+ "plugin loaded\n");
+
+ return err;
+}
+
diff --git a/ldap/servers/plugins/uiduniq/Makefile b/ldap/servers/plugins/uiduniq/Makefile
new file mode 100644
index 00000000..79b95995
--- /dev/null
+++ b/ldap/servers/plugins/uiduniq/Makefile
@@ -0,0 +1,99 @@
+#
+# BEGIN COPYRIGHT BLOCK
+# Copyright 2001 Sun Microsystems, Inc.
+# Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+# All rights reserved.
+# END COPYRIGHT BLOCK
+#
+#
+# GNU Makefile for Directory Server "Pass Through Authentication" plugin
+#
+#
+
+LDAP_SRC = ../../..
+MCOM_ROOT = ../../../../..
+
+NOSTDCLEAN=true # don't let nsconfig.mk define target clean
+NOSTDSTRIP=true # don't let nsconfig.mk define target strip
+NSPR20=true # probably should be defined somewhere else (not sure where)
+
+OBJDEST = $(OBJDIR)/lib/libuidunique
+LIBDIR = $(LIB_RELDIR)
+SHAREDLIB = $(OBJDIR)/lib/shared/utils.o
+
+include $(MCOM_ROOT)/ldapserver/nsconfig.mk
+include $(LDAP_SRC)/nsldap.mk
+
+ifeq ($(ARCH), WINNT)
+DEF_FILE:=./libuiduniq.def
+endif
+
+CFLAGS+=$(SLCFLAGS)
+
+INCLUDES += -I$(LDAP_SRC)/servers/slapd -I../shared
+
+LOCAL_OBJS= uid.o 7bit.o
+
+SHAREDDIR= ../shared
+
+OBJS = $(addprefix $(OBJDEST)/, $(LOCAL_OBJS))
+
+ifeq ($(ARCH), WINNT)
+#LIBUIDUNIQUE_DLL_OBJ = $(addprefix $(OBJDEST)/, uid.o 7bit.o)
+endif
+
+LIBUIDUNIQUE= $(addprefix $(LIBDIR)/, $(UID_DLL).$(DLL_SUFFIX))
+
+ifeq ($(ARCH), WINNT)
+EXTRA_LIBS_DEP += $(LIBSLAPD_DEP)
+EXTRA_LIBS_DEP += $(LDAPSDK_DEP)
+EXTRA_LIBS += $(LIBSLAPD) $(LDAP_SDK_LIBLDAP_DLL)
+endif
+
+
+
+ifeq ($(ARCH), WINNT)
+DLL_LDFLAGS += -def:"./libuiduniq.def"
+endif # WINNT
+
+ifeq ($(ARCH), AIX)
+EXTRA_LIBS_DEP += $(LIBSLAPD_DEP)
+EXTRA_LIBS_DEP += $(LDAPSDK_DEP)
+EXTRA_LIBS += $(LIBSLAPD) $(LDAP_SDK_LIBLDAP_DLL)
+EXTRA_LIBS += $(DLL_EXTRA_LIBS)
+LD=ld
+endif
+
+ifeq ($(ARCH), HPUX)
+EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAPSDK_DEP) $(NSPR_DEP) $(SECURITY_DEP)
+EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(SECURITYLINK) $(NSPRLINK) $(ICULINK)
+endif
+
+EXTRA_LIBS += $(SHAREDLIB)
+
+clientSDK:
+
+all: $(OBJDEST) $(LIBDIR) $(LIBUIDUNIQUE)
+
+$(LIBUIDUNIQUE): $(OBJS) $(LIBUIDUNIQUE_DLL_OBJ) $(DEF_FILE)
+# $(LINK_DLL) $(LIBUIDUNIQUE_DLL_OBJ) $(PLATFORMLIBS) $(EXTRA_LIBS)
+ $(LINK_DLL) $(PLATFORMLIBS) $(EXTRA_LIBS)
+
+veryclean: clean
+
+clean:
+ $(RM) $(OBJS)
+ifeq ($(ARCH), WINNT)
+ $(RM) $(LIBUIDUNIQUE_DLL_OBJ)
+endif
+ $(RM) $(LIBUIDUNIQUE)
+
+$(OBJDEST):
+ $(MKDIR) $(OBJDEST)
+
+#
+# header file dependencies (incomplete)
+#
+$(OBJS): $(LDAP_SRC)/servers/slapd/slapi-plugin.h \
+ ../shared/plugin-utils.h
+
diff --git a/ldap/servers/plugins/uiduniq/UID-Notes b/ldap/servers/plugins/uiduniq/UID-Notes
new file mode 100644
index 00000000..3d3617ff
--- /dev/null
+++ b/ldap/servers/plugins/uiduniq/UID-Notes
@@ -0,0 +1,93 @@
+#
+# BEGIN COPYRIGHT BLOCK
+# Copyright 2001 Sun Microsystems, Inc.
+# Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+# All rights reserved.
+# END COPYRIGHT BLOCK
+#
+Unique UID Checking Plugin
+--------------------------
+
+Terry Hayes, April 16, 1998
+
+
+GOALS
+
+The Unique UID Checking Plugin supports the management of user entries in the
+directory by enforcing the constraints on the value of an attribute within a
+portion of the directory. This provides a central point for enforcing this
+constraint, which allows changes from any source to be checked (DSGW, Kingpin,
+LDAP utilities, or user application).
+
+CONFIGURATION
+
+The software operates as a preoperation plugin to the directory server. An
+entry must be added to the slapd.conf file for the server that declares the
+plugin and provides arguments required for its operation.
+
+The plugin is declared as follows (line split for clarity):
+
+ plugin preoperation "uid uniqueness" /home/thayes/testdir/lib/uid-plugin.so
+ uidunique_init <attribute_name> <subtree_dn> ...
+
+The first 5 values are the standard plugin declaration. The uidunique_init
+function registers preoperation callbacks for the add, modify and modRDN
+directory operations.
+
+The next argument ("attribute_name") specifies the name of the entry attribute
+to check for uniqueness. This attribute must be unique within each of the
+subtrees listed in the remainder of the arguments.
+
+For example:
+
+ plugin preoperation "uid uniqueness" /home/thayes/testdir/lib/uid-plugin.so
+ uidunique_init uid o=mcom.com
+
+This line specifies "uid" as the unique attribute, and lists a single subtree
+to be checked. This line is typical of an initial installation (see below).
+
+A more complex case:
+
+ plugin preoperation "uid uniqueness" /home/thayes/testdir/lib/uid-plugin.so
+ uidunique_init uid o=Coke o=Pepsi
+ plugin preoperation "uid uniqueness" /home/thayes/testdir/lib/uid-plugin.so
+ uidunique_init mail "o=Dr. Pepper"
+
+This configuration specifies a total of three subtrees to check. Two use the
+(standard) "uid" attribute as a unique value. The other specifies "mail"
+as the unique attribute.
+
+INSTALLATION
+
+The standard installation of the directory server will configure this plugin
+to check the "uid" attribute on the default suffix.
+
+OPERATION
+
+The plugin responds to the following LDAP operations:
+
+ + add
+ + modify
+ + modRDN
+
+For all operations, the plugin forces the LDAP operation to return
+CONSTRAINT_VIOLATION if the operation would result in two entries with
+the same unique attribute value.
+
+For an "add" operation that includes the unique attribute, the plugin checks
+that no other entry has the same value.
+
+For a "modify" operation, the operation will fail if the new value of the
+attribute exists in any entry OTHER than the target of the modify. If the
+value already exists, but is in the node being changed, the operation
+succeeds. For example, if a modify operation replaces a 'uid' attribute
+with the same set of values, the plugin will find the "new" values already
+exist. However since it is in the entry being modified, the operation is
+allowed to complete.
+
+For modRDN, the same checking as for "modify" is performed.
+
+ModRDN is coded to handle reparenting, but since the LDAP protocol to support
+this operation is not present, it cannot be exercised and has not been
+tested.
+
diff --git a/ldap/servers/plugins/uiduniq/libuiduniq.def b/ldap/servers/plugins/uiduniq/libuiduniq.def
new file mode 100644
index 00000000..52bbfcca
--- /dev/null
+++ b/ldap/servers/plugins/uiduniq/libuiduniq.def
@@ -0,0 +1,15 @@
+; BEGIN COPYRIGHT BLOCK
+; Copyright 2001 Sun Microsystems, Inc.
+; Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+; All rights reserved.
+; END COPYRIGHT BLOCK
+;
+;
+;
+DESCRIPTION 'Netscape Directory Server 7 Unique Attribute Checking Plugin'
+;CODE SHARED READ EXECUTE
+;DATA SHARED READ WRITE
+EXPORTS
+ uidunique_init @1
+ NSUniqueAttr_Init @2
+ NS7bitAttr_Init @3
diff --git a/ldap/servers/plugins/uiduniq/uid.c b/ldap/servers/plugins/uiduniq/uid.c
new file mode 100644
index 00000000..f32e63ac
--- /dev/null
+++ b/ldap/servers/plugins/uiduniq/uid.c
@@ -0,0 +1,1073 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/*
+ * uid.c
+ *
+ * Implements a directory server pre-operation plugin to test
+ * attributes for uniqueness within a defined subtree in the
+ * directory.
+ *
+ * Called uid.c since the original purpose of the plugin was to
+ * check the uid attribute in user entries.
+ */
+#include <slapi-plugin.h>
+#include <portable.h>
+#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */
+#include <string.h>
+#include "dirver.h"
+#include "plugin-utils.h"
+
+#if defined( LDAP_DEBUG ) && !defined( DEBUG )
+#define DEBUG
+#endif
+
+#define UNTAGGED_PARAMETER 12
+
+/* Quoting routine - this should be in a library somewhere (slapi?) */
+int ldap_quote_filter_value(
+ char *value, int len,
+ char *out, int maxLen,
+ int *outLen);
+
+
+static int search_one_berval(const char *baseDN, const char *attrName,
+ const struct berval *value, const char *target);
+
+/*
+ * ISSUES:
+ * How should this plugin handle ACL issues? It seems wrong to reject
+ * adds and modifies because there is already a conflicting UID, when
+ * the request would have failed because of an ACL check anyway.
+ *
+ * This code currently defines a maximum filter string size of 512. Is
+ * this large enough?
+ *
+ * This code currently does not quote the value portion of the filter as
+ * it is created. This is a bug.
+ */
+
+/* */
+#define BEGIN do {
+#define END } while(0);
+
+/*
+ * Slapi plugin descriptor
+ */
+static char *plugin_name = "NSUniqueAttr";
+static Slapi_PluginDesc
+pluginDesc = {
+ "NSUniqueAttr", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT,
+ "Enforce unique attribute values"
+};
+static void* plugin_identity = NULL;
+
+
+/*
+ * More information about constraint failure
+ */
+static char *moreInfo =
+ "Another entry with the same attribute value already exists";
+
+static void
+freePblock( Slapi_PBlock *spb ) {
+ if ( spb )
+ {
+ slapi_free_search_results_internal( spb );
+ slapi_pblock_destroy( spb );
+ }
+}
+
+/* ------------------------------------------------------------ */
+/*
+ * op_error - Record (and report) an operational error.
+ * name changed to uid_op_error so as not to conflict with the external function
+ * of the same name thereby preventing compiler warnings.
+ */
+static int
+uid_op_error(int internal_error)
+{
+ slapi_log_error(
+ SLAPI_LOG_PLUGIN,
+ plugin_name,
+ "Internal error: %d\n",
+ internal_error);
+
+ return LDAP_OPERATIONS_ERROR;
+}
+
+/* ------------------------------------------------------------ */
+/*
+ * Create an LDAP search filter from the attribute
+ * name and value supplied.
+ */
+
+static char *
+create_filter(const char *attribute, const struct berval *value)
+{
+ char *filter;
+ char *fp;
+ char *max;
+ int attrLen;
+ int valueLen;
+ int filterLen;
+
+ /* Compute the length of the required buffer */
+ attrLen = strlen(attribute);
+
+ if (ldap_quote_filter_value(value->bv_val,
+ value->bv_len, 0, 0, &valueLen))
+ return 0;
+
+ filterLen = attrLen + 1 + valueLen + 1;
+
+ /* Allocate the buffer */
+ filter = slapi_ch_malloc(filterLen);
+ fp = filter;
+ max = &filter[filterLen];
+
+ /* Place attribute name in filter */
+ strcpy(fp, attribute);
+ fp += attrLen;
+
+ /* Place comparison operator */
+ *fp++ = '=';
+
+ /* Place value in filter */
+ if (ldap_quote_filter_value(value->bv_val, value->bv_len,
+ fp, max-fp, &valueLen)) { slapi_ch_free((void**)&filter); return 0; }
+ fp += valueLen;
+
+ /* Terminate */
+ *fp = 0;
+
+ return filter;
+}
+
+/* ------------------------------------------------------------ */
+/*
+ * search - search a subtree for entries with a named attribute matching
+ * the list of values. An entry matching the 'target' DN is
+ * not considered in the search.
+ *
+ * If 'attr' is NULL, the values are taken from 'values'.
+ * If 'attr' is non-NULL, the values are taken from 'attr'.
+ *
+ * Return:
+ * LDAP_SUCCESS - no matches, or the attribute matches the
+ * target dn.
+ * LDAP_CONSTRAINT_VIOLATION - an entry was found that already
+ * contains the attribute value.
+ * LDAP_OPERATIONS_ERROR - a server failure.
+ */
+static int
+search(const char *baseDN, const char *attrName, Slapi_Attr *attr,
+ struct berval **values, const char *target)
+{
+ int result;
+
+#ifdef DEBUG
+ slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
+ "SEARCH baseDN=%s attr=%s target=%s\n", baseDN, attrName,
+ target?target:"None");
+#endif
+
+ result = LDAP_SUCCESS;
+
+ /* If no values, can't possibly be a conflict */
+ if ( (Slapi_Attr *)NULL == attr && (struct berval **)NULL == values )
+ return result;
+
+ /*
+ * Perform the search for each value provided
+ *
+ * Another possibility would be to search for all the values at once.
+ * However, this is more complex (for filter creation) and unique
+ * attributes values are probably only changed one at a time anyway.
+ */
+ if ( (Slapi_Attr *)NULL != attr )
+ {
+ Slapi_Value *v = NULL;
+ int vhint = -1;
+
+ for ( vhint = slapi_attr_first_value( attr, &v );
+ vhint != -1 && LDAP_SUCCESS == result;
+ vhint = slapi_attr_next_value( attr, vhint, &v ))
+ {
+ result = search_one_berval(baseDN,attrName,
+ slapi_value_get_berval(v),target);
+ }
+ }
+ else
+ {
+ for (;*values != NULL && LDAP_SUCCESS == result; values++)
+ {
+ result = search_one_berval(baseDN,attrName,*values,target);
+ }
+ }
+
+#ifdef DEBUG
+ slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
+ "SEARCH result = %d\n", result);
+#endif
+
+ return( result );
+}
+
+
+static int
+search_one_berval(const char *baseDN, const char *attrName,
+ const struct berval *value, const char *target)
+{
+ int result;
+ char *filter;
+ Slapi_PBlock *spb;
+
+ result = LDAP_SUCCESS;
+
+ /* If no value, can't possibly be a conflict */
+ if ( (struct berval *)NULL == value )
+ return result;
+
+ filter = 0;
+ spb = 0;
+
+ BEGIN
+ int err;
+ int sres;
+ Slapi_Entry **entries;
+ static char *attrs[] = { "1.1", 0 };
+
+ /* Create the filter - this needs to be freed */
+ filter = create_filter(attrName, value);
+
+#ifdef DEBUG
+ slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
+ "SEARCH filter=%s\n", filter);
+#endif
+
+ /* Perform the search using the new internal API */
+ spb = slapi_pblock_new();
+ if (!spb) { result = uid_op_error(2); break; }
+
+ slapi_search_internal_set_pb(spb, baseDN, LDAP_SCOPE_SUBTREE,
+ filter, attrs, 0 /* attrs only */, NULL, NULL, plugin_identity, 0 /* actions */);
+ slapi_search_internal_pb(spb);
+
+ err = slapi_pblock_get(spb, SLAPI_PLUGIN_INTOP_RESULT, &sres);
+ if (err) { result = uid_op_error(3); break; }
+
+ /* Allow search to report that there is nothing in the subtree */
+ if (sres == LDAP_NO_SUCH_OBJECT) break;
+
+ /* Other errors are bad */
+ if (sres) { result = uid_op_error(3); break; }
+
+ err = slapi_pblock_get(spb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES,
+ &entries);
+ if (err) { result = uid_op_error(4); break; }
+
+ /*
+ * Look at entries returned. Any entry found must be the
+ * target entry or the constraint fails.
+ */
+ for(;*entries;entries++)
+ {
+ char *dn = slapi_entry_get_dn(*entries);
+
+ /*
+ * DNs are returned in the original value used to insert
+ * the entry. This must be "normalized" for comparison.
+ *
+ * This normalization is done "in-place" (modifying the value
+ * in the entry). This is OK, since this is the only user
+ * of this copy of the entry.
+ */
+ slapi_dn_normalize_case(dn);
+
+#ifdef DEBUG
+ slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
+ "SEARCH entry dn=%s\n", dn);
+#endif
+
+ /*
+ * It is a Constraint Violation if any entry is found, unless
+ * the entry is the target entry (if any).
+ */
+ if (!target || strcmp(dn, target) != 0)
+ {
+ result = LDAP_CONSTRAINT_VIOLATION;
+ break;
+ }
+ }
+
+#ifdef DEBUG
+ slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
+ "SEARCH complete result=%d\n", result);
+#endif
+ END
+
+ /* Clean-up */
+ if (spb) {
+ slapi_free_search_results_internal(spb);
+ slapi_pblock_destroy(spb);
+ }
+
+ slapi_ch_free((void**)&filter);
+
+ return result;
+}
+
+/* ------------------------------------------------------------ */
+/*
+ * searchAllSubtrees - search all subtrees in argv for entries
+ * with a named attribute matching the list of values, by
+ * calling search for each one.
+ *
+ * If 'attr' is NULL, the values are taken from 'values'.
+ * If 'attr' is non-NULL, the values are taken from 'attr'.
+ *
+ * Return:
+ * LDAP_SUCCESS - no matches, or the attribute matches the
+ * target dn.
+ * LDAP_CONSTRAINT_VIOLATION - an entry was found that already
+ * contains the attribute value.
+ * LDAP_OPERATIONS_ERROR - a server failure.
+ */
+static int
+searchAllSubtrees(int argc, char *argv[], const char *attrName,
+ Slapi_Attr *attr, struct berval **values, const char *dn)
+{
+ int result = LDAP_SUCCESS;
+
+ /*
+ * For each DN in the managed list, do uniqueness checking if
+ * the target DN is a subnode in the tree.
+ */
+ for(;argc > 0;argc--,argv++)
+ {
+ result = search(*argv, attrName, attr, values, dn);
+ if (result) break;
+ }
+ return result;
+}
+
+/* ------------------------------------------------------------ */
+/*
+ * getArguments - parse invocation parameters
+ * Return:
+ * 0 - success
+ * >0 - error parsing parameters
+ */
+static int
+getArguments(Slapi_PBlock *pb, char **attrName, char **markerObjectClass,
+ char **requiredObjectClass)
+{
+ int argc;
+ char **argv;
+
+ /*
+ * Get the arguments
+ */
+ if (slapi_pblock_get(pb, SLAPI_PLUGIN_ARGC, &argc))
+ {
+ return uid_op_error(10);
+ }
+
+ if (slapi_pblock_get(pb, SLAPI_PLUGIN_ARGV, &argv))
+ {
+ return uid_op_error(11);
+ }
+
+ /*
+ * Required arguments: attribute and markerObjectClass
+ * Optional argument: requiredObjectClass
+ */
+ for(;argc > 0;argc--,argv++)
+ {
+ char *param = *argv;
+ char *delimiter = strchr(param, '=');
+ if (NULL == delimiter)
+ {
+ /* Old style untagged parameter */
+ *attrName = *argv;
+ return UNTAGGED_PARAMETER;
+ }
+ if (strncasecmp(param, "attribute", delimiter-param) == 0)
+ {
+ /* It's OK to set a pointer here, because ultimately it points
+ * inside the argv array of the pblock, which will be staying
+ * arround.
+ */
+ *attrName = delimiter+1;
+ } else if (strncasecmp(param, "markerobjectclass", delimiter-param) == 0)
+ {
+ *markerObjectClass = delimiter+1;
+ } else if (strncasecmp(param, "requiredobjectclass", delimiter-param) == 0)
+ {
+ *requiredObjectClass = delimiter+1;
+ }
+ }
+ if (!*attrName || !*markerObjectClass)
+ {
+ return uid_op_error(13);
+ }
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/*
+ * findSubtreeAndSearch - walk up the tree to find an entry with
+ * the marker object class; if found, call search from there and
+ * return the result it returns
+ *
+ * If 'attr' is NULL, the values are taken from 'values'.
+ * If 'attr' is non-NULL, the values are taken from 'attr'.
+ *
+ * Return:
+ * LDAP_SUCCESS - no matches, or the attribute matches the
+ * target dn.
+ * LDAP_CONSTRAINT_VIOLATION - an entry was found that already
+ * contains the attribute value.
+ * LDAP_OPERATIONS_ERROR - a server failure.
+ */
+static int
+findSubtreeAndSearch(char *parentDN, const char *attrName, Slapi_Attr *attr,
+ struct berval **values, const char *target, const char *markerObjectClass)
+{
+ int result = LDAP_SUCCESS;
+ Slapi_PBlock *spb = NULL;
+
+ while (NULL != (parentDN = slapi_dn_parent(parentDN)))
+ {
+ if (spb = dnHasObjectClass(parentDN, markerObjectClass))
+ {
+ freePblock(spb);
+ /*
+ * Do the search. There is no entry that is allowed
+ * to have the attribute already.
+ */
+ result = search(parentDN, attrName, attr, values, target);
+ break;
+ }
+ }
+ return result;
+}
+
+
+/* ------------------------------------------------------------ */
+/*
+ * preop_add - pre-operation plug-in for add
+ */
+static int
+preop_add(Slapi_PBlock *pb)
+{
+ int result;
+
+#ifdef DEBUG
+ slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, "ADD begin\n");
+#endif
+
+ result = LDAP_SUCCESS;
+
+ /*
+ * Do constraint check on the added entry. Set result.
+ */
+
+ BEGIN
+ int err;
+ char *attrName = NULL;
+ char *markerObjectClass = NULL;
+ char *requiredObjectClass = NULL;
+ char *dn;
+ int isupdatedn;
+ Slapi_Entry *e;
+ Slapi_Attr *attr;
+ int argc;
+ char **argv = NULL;
+
+ /*
+ * If this is a replication update, just be a noop.
+ */
+ err = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &isupdatedn);
+ if (err) { result = uid_op_error(50); break; }
+ if (isupdatedn)
+ {
+ break;
+ }
+
+ /*
+ * Get the arguments
+ */
+ result = getArguments(pb, &attrName, &markerObjectClass,
+ &requiredObjectClass);
+ if (UNTAGGED_PARAMETER == result)
+ {
+ slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
+ "ADD parameter untagged: %s\n", attrName);
+ result = LDAP_SUCCESS;
+ /* Statically defined subtrees to monitor */
+ err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGC, &argc);
+ if (err) { result = uid_op_error(53); break; }
+
+ err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGV, &argv);
+ if (err) { result = uid_op_error(54); break; }
+ argc--; argv++; /* First argument was attribute name */
+ } else if (0 != result)
+ {
+ break;
+ }
+
+ /*
+ * Get the target DN for this add operation
+ */
+ err = slapi_pblock_get(pb, SLAPI_ADD_TARGET, &dn);
+ if (err) { result = uid_op_error(51); break; }
+
+#ifdef DEBUG
+ slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, "ADD target=%s\n", dn);
+#endif
+
+ /*
+ * Get the entry data for this add. Check whether it
+ * contains a value for the unique attribute
+ */
+ err = slapi_pblock_get(pb, SLAPI_ADD_ENTRY, &e);
+ if (err) { result = uid_op_error(52); break; }
+
+ err = slapi_entry_attr_find(e, attrName, &attr);
+ if (err) break; /* no unique attribute */
+
+ /*
+ * Check if it contains the required object class
+ */
+ if (NULL != requiredObjectClass)
+ {
+ if (!entryHasObjectClass(pb, e, requiredObjectClass))
+ {
+ /* No, so we don't have to do anything */
+ break;
+ }
+ }
+
+ /*
+ * Passed all the requirements - this is an operation we
+ * need to enforce uniqueness on. Now find all parent entries
+ * with the marker object class, and do a search for each one.
+ */
+ if (NULL != markerObjectClass)
+ {
+ /* Subtree defined by location of marker object class */
+ result = findSubtreeAndSearch(dn, attrName, attr, NULL,
+ dn, markerObjectClass);
+ } else
+ {
+ /* Subtrees listed on invocation line */
+ result = searchAllSubtrees(argc, argv, attrName, attr, NULL, dn);
+ }
+ END
+
+ if (result)
+ {
+ slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
+ "ADD result %d\n", result);
+
+ /* Send failure to the client */
+ slapi_send_ldap_result(pb, result, 0, moreInfo, 0, 0);
+ }
+
+ return (result==LDAP_SUCCESS)?0:-1;
+}
+
+static void
+addMod(LDAPMod ***modary, int *capacity, int *nmods, LDAPMod *toadd)
+{
+ if (*nmods == *capacity) {
+ *capacity += 4;
+ if (*modary) {
+ *modary = (LDAPMod **)slapi_ch_realloc((char *)*modary, *capacity * sizeof(LDAPMod *));
+ } else {
+ *modary = (LDAPMod **)slapi_ch_malloc(*capacity * sizeof(LDAPMod *));
+ }
+ }
+ *modary[*nmods] = toadd;
+ (*nmods)++;
+}
+
+/* ------------------------------------------------------------ */
+/*
+ * preop_modify - pre-operation plug-in for modify
+ */
+static int
+preop_modify(Slapi_PBlock *pb)
+{
+
+ int result = LDAP_SUCCESS;
+ Slapi_PBlock *spb = NULL;
+ LDAPMod **checkmods = NULL;
+ int checkmodsCapacity = 0;
+
+#ifdef DEBUG
+ slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
+ "MODIFY begin\n");
+#endif
+
+ BEGIN
+ int err;
+ char *attrName;
+ char *markerObjectClass=NULL;
+ char *requiredObjectClass=NULL;
+ LDAPMod **mods;
+ int modcount = 0;
+ int ii;
+ LDAPMod *mod;
+ char *dn;
+ int isupdatedn;
+ int argc;
+ char **argv = NULL;
+
+ /*
+ * If this is a replication update, just be a noop.
+ */
+ err = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &isupdatedn);
+ if (err) { result = uid_op_error(60); break; }
+ if (isupdatedn)
+ {
+ break;
+ }
+
+ /*
+ * Get the arguments
+ */
+ result = getArguments(pb, &attrName, &markerObjectClass,
+ &requiredObjectClass);
+ if (UNTAGGED_PARAMETER == result)
+ {
+ result = LDAP_SUCCESS;
+ /* Statically defined subtrees to monitor */
+ err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGC, &argc);
+ if (err) { result = uid_op_error(53); break; }
+
+ err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGV, &argv);
+ if (err) { result = uid_op_error(54); break; }
+ argc--; /* First argument was attribute name */
+ argv++;
+ } else if (0 != result)
+ {
+ break;
+ }
+
+ err = slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods);
+ if (err) { result = uid_op_error(61); break; }
+
+ /* There may be more than one mod that matches e.g.
+ changetype: modify
+ delete: uid
+ uid: balster1950
+ -
+ add: uid
+ uid: scottg
+
+ So, we need to first find all mods that contain the attribute
+ which are add or replace ops and are bvalue encoded
+ */
+ /* find out how many mods meet this criteria */
+ for(;*mods;mods++)
+ {
+ mod = *mods;
+ if ((slapi_attr_type_cmp(mod->mod_type, attrName, 1) == 0) && /* mod contains target attr */
+ (mod->mod_op & LDAP_MOD_BVALUES) && /* mod is bval encoded (not string val) */
+ (mod->mod_bvalues && mod->mod_bvalues[0]) && /* mod actually contains some values */
+ (((mod->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_ADD) || /* mod is add */
+ (mod->mod_op & LDAP_MOD_REPLACE))) /* mod is replace */
+ {
+ addMod(&checkmods, &checkmodsCapacity, &modcount, mod);
+ }
+ }
+ if (modcount == 0) {
+ break; /* no mods to check, we are done */
+ }
+
+ /* Get the target DN */
+ err = slapi_pblock_get(pb, SLAPI_MODIFY_TARGET, &dn);
+ if (err) { result = uid_op_error(11); break; }
+
+ if (requiredObjectClass &&
+ !(spb = dnHasObjectClass(dn, requiredObjectClass))) { break; }
+
+ /*
+ * Passed all the requirements - this is an operation we
+ * need to enforce uniqueness on. Now find all parent entries
+ * with the marker object class, and do a search for each one.
+ */
+ /*
+ * stop checking at first mod that fails the check
+ */
+ for (ii = 0; (result == 0) && (ii < modcount); ++ii)
+ {
+ mod = checkmods[ii];
+ if (NULL != markerObjectClass)
+ {
+ /* Subtree defined by location of marker object class */
+ result = findSubtreeAndSearch(dn, attrName, NULL,
+ mod->mod_bvalues, dn,
+ markerObjectClass);
+ } else
+ {
+ /* Subtrees listed on invocation line */
+ result = searchAllSubtrees(argc, argv, attrName, NULL,
+ mod->mod_bvalues, dn);
+ }
+ }
+ END
+
+ slapi_ch_free((void **)&checkmods);
+ freePblock(spb);
+ if (result)
+ {
+ slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
+ "MODIFY result %d\n", result);
+
+ slapi_send_ldap_result(pb, result, 0, moreInfo, 0, 0);
+ }
+
+ return (result==LDAP_SUCCESS)?0:-1;
+
+}
+
+/* ------------------------------------------------------------ */
+/*
+ * preop_modrdn - Pre-operation call for modify RDN
+ *
+ * Check that the new RDN does not include attributes that
+ * cause a constraint violation
+ */
+static int
+preop_modrdn(Slapi_PBlock *pb)
+{
+ int result;
+ Slapi_Entry *e;
+
+#ifdef DEBUG
+ slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
+ "MODRDN begin\n");
+#endif
+
+ /* Init */
+ result = LDAP_SUCCESS;
+ e = 0;
+
+ BEGIN
+ int err;
+ char *attrName;
+ char *markerObjectClass=NULL;
+ char *requiredObjectClass=NULL;
+ char *dn;
+ char *superior;
+ char *rdn;
+ int isupdatedn;
+ Slapi_Attr *attr;
+ int argc;
+ char **argv = NULL;
+
+ /*
+ * If this is a replication update, just be a noop.
+ */
+ err = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &isupdatedn);
+ if (err) { result = uid_op_error(30); break; }
+ if (isupdatedn)
+ {
+ break;
+ }
+
+ /*
+ * Get the arguments
+ */
+ result = getArguments(pb, &attrName, &markerObjectClass,
+ &requiredObjectClass);
+ if (UNTAGGED_PARAMETER == result)
+ {
+ result = LDAP_SUCCESS;
+ /* Statically defined subtrees to monitor */
+ err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGC, &argc);
+ if (err) { result = uid_op_error(53); break; }
+
+ err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGV, &argv);
+ if (err) { result = uid_op_error(54); break; }
+ argc--; /* First argument was attribute name */
+ argv++;
+ } else if (0 != result)
+ {
+ break;
+ }
+
+ /* Get the DN of the entry being renamed */
+ err = slapi_pblock_get(pb, SLAPI_MODRDN_TARGET, &dn);
+ if (err) { result = uid_op_error(31); break; }
+
+ /* Get superior value - unimplemented in 3.0/4.0/5.0 DS */
+ err = slapi_pblock_get(pb, SLAPI_MODRDN_NEWSUPERIOR, &superior);
+ if (err) { result = uid_op_error(32); break; }
+
+ /*
+ * No superior means the entry is just renamed at
+ * its current level in the tree. Use the target DN for
+ * determining which managed tree this belongs to
+ */
+ if (!superior) superior = dn;
+
+ /* Get the new RDN - this has the attribute values */
+ err = slapi_pblock_get(pb, SLAPI_MODRDN_NEWRDN, &rdn);
+ if (err) { result = uid_op_error(33); break; }
+#ifdef DEBUG
+ slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
+ "MODRDN newrdn=%s\n", rdn);
+#endif
+
+ /*
+ * Parse the RDN into attributes by creating a "dummy" entry
+ * and setting the attributes from the RDN.
+ *
+ * The new entry must be freed.
+ */
+ e = slapi_entry_alloc();
+ if (!e) { result = uid_op_error(34); break; }
+
+ /* NOTE: strdup on the rdn, since it will be freed when
+ * the entry is freed */
+
+ slapi_entry_set_dn(e, slapi_ch_strdup(rdn));
+
+ err = slapi_entry_add_rdn_values(e);
+ if (err)
+ {
+ slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
+ "MODRDN bad rdn value=%s\n", rdn);
+ break; /* Bad DN */
+ }
+
+ /*
+ * Find any unique attribute data in the new RDN
+ */
+ err = slapi_entry_attr_find(e, attrName, &attr);
+ if (err) break; /* no UID attribute */
+
+ /*
+ * Passed all the requirements - this is an operation we
+ * need to enforce uniqueness on. Now find all parent entries
+ * with the marker object class, and do a search for each one.
+ */
+ if (NULL != markerObjectClass)
+ {
+ /* Subtree defined by location of marker object class */
+ result = findSubtreeAndSearch(dn, attrName, attr, NULL, dn,
+ markerObjectClass);
+ } else
+ {
+ /* Subtrees listed on invocation line */
+ result = searchAllSubtrees(argc, argv, attrName, attr, NULL, dn);
+ }
+ END
+ /* Clean-up */
+ if (e) slapi_entry_free(e);
+
+ if (result)
+ {
+ slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
+ "MODRDN result %d\n", result);
+
+ slapi_send_ldap_result(pb, result, 0, moreInfo, 0, 0);
+ }
+
+ return (result==LDAP_SUCCESS)?0:-1;
+
+}
+
+/* ------------------------------------------------------------ */
+/*
+ * Initialize the plugin
+ *
+ * uidunique_init (the old name) is deprecated
+ */
+int
+NSUniqueAttr_Init(Slapi_PBlock *pb)
+{
+ int err = 0;
+
+ BEGIN
+ int err;
+ int argc;
+ char **argv;
+
+ /* Declare plugin version */
+ err = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION,
+ SLAPI_PLUGIN_VERSION_01);
+ if (err) break;
+
+ /*
+ * Get plugin identity and store it for later use
+ * Used for internal operations
+ */
+
+ slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &plugin_identity);
+ /* PR_ASSERT (plugin_identity); */
+
+ /*
+ * Get and normalize arguments
+ */
+ err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGC, &argc);
+ if (err) break;
+
+ err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGV, &argv);
+ if (err) break;
+
+ /* First argument is the unique attribute name */
+ if (argc < 1) { err = -1; break; }
+ argv++; argc--;
+
+ for(;argc > 0;argc--, argv++)
+ slapi_dn_normalize_case(*argv);
+
+ /* Provide descriptive information */
+ err = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION,
+ (void*)&pluginDesc);
+ if (err) break;
+
+ /* Register functions */
+ err = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_ADD_FN,
+ (void*)preop_add);
+ if (err) break;
+
+ err = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_MODIFY_FN,
+ (void*)preop_modify);
+ if (err) break;
+
+ err = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_MODRDN_FN,
+ (void*)preop_modrdn);
+ if (err) break;
+
+ END
+
+ if (err) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, "NSUniqueAttr_Init",
+ "Error: %d\n", err);
+ err = -1;
+ }
+ else
+ slapi_log_error(SLAPI_LOG_PLUGIN, "NSUniqueAttr_Init",
+ "plugin loaded\n");
+
+ return err;
+}
+
+int
+uidunique_init(Slapi_PBlock *pb)
+{
+ return NSUniqueAttr_Init(pb);
+}
+
+
+/* ------------------------------------------------------------ */
+/*
+ * ldap_quote_filter_value
+ *
+ * Quote the filter value according to RFC 2254 (Dec 1997)
+ *
+ * value - a UTF8 string containing the value. It may contain
+ * any of the chars needing quotes ( '(' ')' '*' '/' and NUL ).
+ * len - the length of the UTF8 value
+ * out - a buffer to recieve the converted value. May be NULL, in
+ * which case, only the length of the output is computed (and placed in
+ * outLen).
+ * maxLen - the size of the output buffer. It is an error if this length
+ * is exceeded. Ignored if out is NULL.
+ * outLen - recieves the size of the output. If an error occurs, this
+ * result is not available.
+ *
+ * Returns
+ * 0 - success
+ * -1 - failure (usually a buffer overflow)
+ */
+int /* Error value */
+ldap_quote_filter_value(
+ char *value, int len,
+ char *out, int maxLen,
+ int *outLen)
+{
+ int err;
+ char *eValue;
+ int resLen;
+#ifdef SLAPI_SUPPORTS_V3_ESCAPING
+ static char hexchars[16] = "0123456789abcdef";
+#endif
+
+ err = 0;
+ eValue = &value[len];
+ resLen = 0;
+
+ /*
+ * Convert each character in the input string
+ */
+ while(value < eValue)
+ {
+ switch(*value)
+ {
+ case '(':
+ case ')':
+ case '*':
+ case '\\':
+#ifdef SLAPI_SUPPORTS_V3_ESCAPING
+ case 0:
+#endif
+ /* Handle characters needing special escape sequences */
+
+ /* Compute size of output */
+#ifdef SLAPI_SUPPORTS_V3_ESCAPING
+ resLen += 3;
+#else
+ resLen += 2;
+#endif
+
+ /* Generate output if requested */
+ if (out)
+ {
+ /* Check for overflow */
+ if (resLen > maxLen) { err = -1; break; }
+
+ *out++ = '\\';
+#ifdef SLAPI_SUPPORTS_V3_ESCAPING
+ *out++ = hexchars[(*value >> 4) & 0xF];
+ *out++ = hexchars[*value & 0xF];
+#else
+ *out++ = *value;
+#endif
+ }
+
+ break;
+
+ default:
+ /* Compute size of output */
+ resLen += 1;
+
+ /* Generate output if requested */
+ if (out)
+ {
+ if (resLen > maxLen) { err = -1; break; }
+ *out++ = *value;
+ }
+
+ break;
+ }
+
+ if (err) break;
+
+ value++;
+ }
+
+ if (!err) *outLen = resLen;
+
+ return err;
+}
diff --git a/ldap/servers/plugins/vattrsp_template/Makefile b/ldap/servers/plugins/vattrsp_template/Makefile
new file mode 100644
index 00000000..0eb9e072
--- /dev/null
+++ b/ldap/servers/plugins/vattrsp_template/Makefile
@@ -0,0 +1,79 @@
+#
+# BEGIN COPYRIGHT BLOCK
+# Copyright 2001 Sun Microsystems, Inc.
+# Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+# All rights reserved.
+# END COPYRIGHT BLOCK
+#
+LDAP_SRC = ../../..
+MCOM_ROOT = ../../../../..
+
+NOSTDCLEAN=true # don't let nsconfig.mk define target clean
+NOSTDSTRIP=true # don't let nsconfig.mk define target strip
+NSPR20=true # probably should be defined somewhere else (not sure where)
+
+OBJDEST = $(OBJDIR)/lib/libvattrsp
+LIBDIR = $(LIB_RELDIR)
+
+include $(MCOM_ROOT)/ldapserver/nsdefs.mk
+include $(MCOM_ROOT)/ldapserver/nsconfig.mk
+include $(LDAP_SRC)/nsldap.mk
+
+ifeq ($(ARCH), WINNT)
+DEF_FILE:=./vattrsp.def
+endif
+
+VATTRSP_OBJS = vattrsp.o
+OBJS = $(addprefix $(OBJDEST)/, $(VATTRSP_OBJS))
+
+VATTRSP_DLL = vattrsp-plugin
+
+INCLUDES += -I../../slapd -I../../../include
+CFLAGS+=$(SLCFLAGS) -DSLAPD_LOGGING
+
+ifeq ($(ARCH), WINNT)
+EXTRA_LIBS_DEP += $(LIBSLAPD) $(NSPR_DEP) $(LDAPSDK_DEP)
+EXTRA_LIBS += $(NSPRLINK) $(LIBSLAPD) $(LDAP_SDK_LIBLDAP_DLL)
+VATTRSP_DLL_OBJ = $(addprefix $(OBJDEST)/, dllmain.o)
+endif
+
+ifeq ($(ARCH), AIX)
+EXTRA_LIBS_DEP += $(LIBSLAPD) $(NSPR_DEP) $(LDAPSDK_DEP)
+EXTRA_LIBS += $(LIBSLAPDLINK) $(NSPRLINK) $(LDAP_SDK_LIBLDAP_DLL)
+LD=ld
+endif
+
+ifeq ($(ARCH), HPUX)
+EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAPSDK_DEP) $(NSPR_DEP) $(SECURITY_DEP)
+EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(SECURITYLINK) $(NSPRLINK) $(ICULINK)
+endif
+
+VATTRSP= $(addprefix $(LIBDIR)/, $(VATTRSP_DLL).$(DLL_SUFFIX))
+
+clientSDK:
+
+all: $(OBJDEST) $(LIBDIR) $(VATTRSP)
+
+ifeq ($(ARCH), WINNT)
+$(VATTRSP): $(OBJS) $(VATTRSP_DLL_OBJ) $(DEF_FILE)
+ $(LINK_DLL) $(VATTRSP_DLL_OBJ) $(EXTRA_LIBS) /DEF:$(DEF_FILE)
+else
+$(VATTRSP): $(OBJS) $(VATTRSP_DLL_OBJ)
+ $(LINK_DLL) $(VATTRSP_DLL_OBJ) $(EXTRA_LIBS)
+endif
+
+
+veryclean: clean
+
+clean:
+ $(RM) $(OBJS)
+ifeq ($(ARCH), WINNT)
+ $(RM) $(VATTRSP_DLL_OBJ)
+endif
+ $(RM) $(VATTRSP)
+
+$(OBJDEST):
+ $(MKDIR) $(OBJDEST)
+
+$(LIBDIR):
+ $(MKDIR) $(LIBDIR)
diff --git a/ldap/servers/plugins/vattrsp_template/dllmain.c b/ldap/servers/plugins/vattrsp_template/dllmain.c
new file mode 100644
index 00000000..fabf8677
--- /dev/null
+++ b/ldap/servers/plugins/vattrsp_template/dllmain.c
@@ -0,0 +1,96 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/*
+ * Microsoft Windows specifics for BACK-LDBM DLL
+ */
+#include "ldap.h"
+#include "lber.h"
+
+
+#ifdef _WIN32
+/* Lifted from Q125688
+ * How to Port a 16-bit DLL to a Win32 DLL
+ * on the MSVC 4.0 CD
+ */
+BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved)
+{
+ WSADATA wsadata;
+
+ switch (fdwReason)
+ {
+ case DLL_PROCESS_ATTACH:
+ /* Code from LibMain inserted here. Return TRUE to keep the
+ DLL loaded or return FALSE to fail loading the DLL.
+
+ You may have to modify the code in your original LibMain to
+ account for the fact that it may be called more than once.
+ You will get one DLL_PROCESS_ATTACH for each process that
+ loads the DLL. This is different from LibMain which gets
+ called only once when the DLL is loaded. The only time this
+ is critical is when you are using shared data sections.
+ If you are using shared data sections for statically
+ allocated data, you will need to be careful to initialize it
+ only once. Check your code carefully.
+
+ Certain one-time initializations may now need to be done for
+ each process that attaches. You may also not need code from
+ your original LibMain because the operating system may now
+ be doing it for you.
+ */
+ /*
+ * 16 bit code calls UnlockData()
+ * which is mapped to UnlockSegment in windows.h
+ * in 32 bit world UnlockData is not defined anywhere
+ * UnlockSegment is mapped to GlobalUnfix in winbase.h
+ * and the docs for both UnlockSegment and GlobalUnfix say
+ * ".. function is oboslete. Segments have no meaning
+ * in the 32-bit environment". So we do nothing here.
+ */
+
+ if( errno = WSAStartup(0x0101, &wsadata ) != 0 )
+ return FALSE;
+
+ break;
+
+ case DLL_THREAD_ATTACH:
+ /* Called each time a thread is created in a process that has
+ already loaded (attached to) this DLL. Does not get called
+ for each thread that exists in the process before it loaded
+ the DLL.
+
+ Do thread-specific initialization here.
+ */
+ break;
+
+ case DLL_THREAD_DETACH:
+ /* Same as above, but called when a thread in the process
+ exits.
+
+ Do thread-specific cleanup here.
+ */
+ break;
+
+ case DLL_PROCESS_DETACH:
+ /* Code from _WEP inserted here. This code may (like the
+ LibMain) not be necessary. Check to make certain that the
+ operating system is not doing it for you.
+ */
+ WSACleanup();
+
+ break;
+ }
+ /* The return value is only used for DLL_PROCESS_ATTACH; all other
+ conditions are ignored. */
+ return TRUE; // successful DLL_PROCESS_ATTACH
+}
+#else
+int CALLBACK
+LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine )
+{
+ /*UnlockData( 0 );*/
+ return( 1 );
+}
+#endif
diff --git a/ldap/servers/plugins/vattrsp_template/vattrsp.c b/ldap/servers/plugins/vattrsp_template/vattrsp.c
new file mode 100644
index 00000000..52534b26
--- /dev/null
+++ b/ldap/servers/plugins/vattrsp_template/vattrsp.c
@@ -0,0 +1,397 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include <stdio.h>
+#include <string.h>
+#include "portable.h"
+#include "nspr.h"
+#include "slapi-plugin.h"
+#include "slapi-private.h"
+#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */
+#include "dirver.h"
+#include "vattr_spi.h"
+
+/* the global plugin handle */
+static volatile vattr_sp_handle *vattr_handle = NULL;
+
+/* get file mode flags for unix */
+#ifndef _WIN32
+#include <sys/stat.h>
+#endif
+
+
+#define VATTRSP_PLUGIN_SUBSYSTEM "vattrsp-template-plugin" /* used for logging */
+
+/* function prototypes */
+int vattrsp_init( Slapi_PBlock *pb );
+static int vattrsp_start( Slapi_PBlock *pb );
+static int vattrsp_close( Slapi_PBlock *pb );
+static int vattrsp_vattr_get(
+ vattr_sp_handle *handle,
+ vattr_context *c,
+ Slapi_Entry *e,
+ char *type,
+ Slapi_ValueSet** results,
+ int *type_name_disposition,
+ char** actual_type_name,
+ int flags,
+ int *free_flags,
+ void *hint
+ );
+static int vattrsp_vattr_compare(
+ vattr_sp_handle *handle,
+ vattr_context *c,
+ Slapi_Entry *e,
+ char *type,
+ Slapi_Value *test_this,
+ int* result,
+ int flags,
+ void *hint
+ );
+static int vattrsp_vattr_types(
+ vattr_sp_handle *handle,
+ Slapi_Entry *e,
+ vattr_type_list_context *type_context,
+ int flags
+ );
+
+
+static Slapi_PluginDesc pdesc = { "vattrsp", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT,
+ "class of service plugin" };
+
+static void * vattrsp_plugin_identity = NULL;
+
+
+#ifdef _WIN32
+int *module_ldap_debug = 0;
+
+void plugin_init_debug_level(int *level_ptr)
+{
+ module_ldap_debug = level_ptr;
+}
+#endif
+
+/*
+ * Plugin identity mgmt
+ * --------------------
+ * Used for internal search api's
+ */
+
+void vattrsp_set_plugin_identity(void * identity)
+{
+ vattrsp_plugin_identity=identity;
+}
+
+void * vattrsp_get_plugin_identity()
+{
+ return vattrsp_plugin_identity;
+}
+
+/*
+ * vattrsp_init
+ * --------
+ * adds our callbacks to the list
+ * this is called even if the plugin is disabled
+ * so do not do anything here which enables the
+ * plugin functionality - also no other plugin has been started yet
+ * so you cant use the functionality of other plugins here
+ *
+ * When does this get called?
+ * At server start up. This is the first function in
+ * the plugin to get called, and no guarantees are made
+ * about whether the init() function of other plugins
+ * have been called. It is really only safe to register
+ * the standard SLAPI_PLUGIN_* interfaces here.
+ *
+ * returns 0 on success
+*/
+int vattrsp_init( Slapi_PBlock *pb )
+{
+ int ret = 0;
+ void * plugin_identity=NULL;
+
+ slapi_log_error( SLAPI_LOG_TRACE, VATTRSP_PLUGIN_SUBSYSTEM,"--> vattrsp_init\n");
+
+ /*
+ * Store the plugin identity for later use.
+ * Used for internal operations
+ */
+
+ slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &plugin_identity);
+ PR_ASSERT (plugin_identity);
+ vattrsp_set_plugin_identity(plugin_identity);
+
+ if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION,
+ SLAPI_PLUGIN_VERSION_01 ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN,
+ (void *) vattrsp_start ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN,
+ (void *) vattrsp_close ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION,
+ (void *)&pdesc ) != 0 )
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, VATTRSP_PLUGIN_SUBSYSTEM,
+ "vattrsp_init: failed to register plugin\n" );
+ ret = -1;
+ }
+
+ slapi_log_error( SLAPI_LOG_TRACE, VATTRSP_PLUGIN_SUBSYSTEM,"<-- vattrsp_init\n");
+ return ret;
+}
+
+
+/*
+ * vattrsp_start
+ * ---------
+ * This is called after vattrsp_init, this is where plugin init starts.
+ * Dependencies on other plugins have been satisfied so
+ * feel free to use them e.g. statechange plugin to keep
+ * an eye on configuration changes
+ *
+ * pb should contain SLAPI_TARGET_DN, which is the dn
+ * of the entry representing this plugin, usually in
+ * cn=plugins, cn=config. Use this to get configuration
+ * specific to your plugin from the entry
+ *
+ * When does this get called?
+ * At server start up, after the vattrsp_init() function is
+ * called and when the start function of all plugins this one
+ * depends on have been called. It is safe to rely
+ * on dependencies from now on e.g. perform search operations
+ *
+ * returns 0 on success
+*/
+int vattrsp_start( Slapi_PBlock *pb )
+{
+ int ret = 0;
+
+ slapi_log_error( SLAPI_LOG_TRACE, VATTRSP_PLUGIN_SUBSYSTEM,"--> vattrsp_start\n");
+
+ /* register this vattr service provider with vattr subsystem */
+ if (slapi_vattrspi_register((vattr_sp_handle **)&vattr_handle,
+ vattrsp_vattr_get,
+ vattrsp_vattr_compare,
+ vattrsp_vattr_types) != 0)
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, VATTRSP_PLUGIN_SUBSYSTEM,
+ "vattrsp_start: cannot register as service provider\n" );
+ ret = -1;
+ goto out;
+ }
+
+ /* register a vattr */
+ /* TODO: change dummyAttr to your attribute type,
+ * or write some configuration code which discovers
+ * the attributes to register
+ */
+ slapi_vattrspi_regattr((vattr_sp_handle *)vattr_handle, "dummyAttr", NULL, NULL);
+
+out:
+ slapi_log_error( SLAPI_LOG_TRACE, VATTRSP_PLUGIN_SUBSYSTEM,"<-- vattrsp_start\n");
+ return ret;
+}
+
+/*
+ * vattrsp_close
+ * ---------
+ * closes down the plugin
+ *
+ * When does this get called?
+ * On server shutdown
+ *
+ * returns 0 on success
+*/
+int vattrsp_close( Slapi_PBlock *pb )
+{
+ slapi_log_error( SLAPI_LOG_TRACE, VATTRSP_PLUGIN_SUBSYSTEM,"--> vattrsp_close\n");
+
+ /* clean up stuff here */
+
+ slapi_log_error( SLAPI_LOG_TRACE, VATTRSP_PLUGIN_SUBSYSTEM,"<-- vattrsp_close\n");
+
+ return 0;
+}
+
+
+/*
+ * vattrsp_vattr_get
+ * -----------------
+ * vattr subsystem is requesting the value(s) for "type"
+ *
+ * When does this get called?
+ * Whenever a client requests the attribute "type"
+ * this may be indirectly by a request for all
+ * attributes
+ *
+ * returns 0 on success
+ */
+int vattrsp_vattr_get(
+ vattr_sp_handle *handle, /* pass through to subsequent vattr calls */
+ vattr_context *c, /* opaque context, pass through to subsequent vattr calls */
+ Slapi_Entry *e, /* the entry that the values are for */
+ char *type, /* the type that the values are requested for */
+ Slapi_ValueSet** results, /* put the values here */
+ int *type_name_disposition, /* whether the type is a sub-type etc. */
+ char** actual_type_name, /* maybe you call this another type */
+ int flags, /* see slapi-plugin.h to support these flags */
+ int *free_flags, /* let vattr subsystem know if you supplied value copies it must free */
+ void *hint /* opaque hint, pass through to subsequent vattr calls */
+ )
+{
+ int ret = SLAPI_VIRTUALATTRS_NOT_FOUND;
+
+ slapi_log_error( SLAPI_LOG_TRACE, VATTRSP_PLUGIN_SUBSYSTEM,"--> vattrsp_vattr_get\n");
+
+ /* usual to schema check an attribute
+ * there may be sanity checks which can
+ * be done prior to this "relatively"
+ * slow function to ensure least work done
+ * to fail
+ */
+ if(!slapi_vattr_schema_check_type(e, type))
+ return ret;
+
+ /* TODO: do your thing, resolve the attribute */
+ /* some vattr sps may look after more than one
+ * attribute, this one does not, so no need to
+ * check against "type", we know its our "dummyAttr"
+ */
+ {
+ /* TODO: replace this with your resolver */
+ Slapi_Value *val = slapi_value_new_string("dummyValue");
+
+ *results = slapi_valueset_new();
+ slapi_valueset_add_value(*results, val);
+
+ ret = 0;
+ }
+
+ if(ret == 0)
+ {
+ /* TODO: set *free_flags
+ * if allocated memory for values
+ * use: SLAPI_VIRTUALATTRS_RETURNED_COPIES
+ *
+ * otherwise, if you can guarantee that
+ * this value will live beyond this operation
+ * use: SLAPI_VIRTUALATTRS_RETURNED_POINTERS
+ */
+ *free_flags = SLAPI_VIRTUALATTRS_RETURNED_COPIES;
+
+ /* say the type is the same as the one requested
+ * could be your vattr needs to switch types, say to
+ * make one type look like another or something, but
+ * that is unusual
+ */
+ *actual_type_name = slapi_ch_strdup(type);
+
+ /* TODO: set *type_name_disposition
+ * if the type matched exactly or it
+ * is an alias for the type then
+ * use: SLAPI_VIRTUALATTRS_TYPE_NAME_MATCHED_EXACTLY_OR_ALIAS
+ *
+ * otherwise, the actual_type_name is a subtype
+ * of type
+ * use: SLAPI_VIRTUALATTRS_TYPE_NAME_MATCHED_SUBTYPE
+ */
+ *type_name_disposition = SLAPI_VIRTUALATTRS_TYPE_NAME_MATCHED_EXACTLY_OR_ALIAS;
+ }
+
+ slapi_log_error( SLAPI_LOG_TRACE, VATTRSP_PLUGIN_SUBSYSTEM,"<-- vattrsp_cache_vattr_get\n");
+ return ret;
+}
+
+
+
+/*
+ * vattrsp_vattr_compare
+ * ---------------------
+ * vattr subsystem is requesting that the values are compared
+ *
+ * When does this get called?
+ * When an LDAP compare operation against this
+ * type is requested by the client
+ *
+ * returns 0 on success
+ */
+int vattrsp_vattr_compare(
+ vattr_sp_handle *handle, /* pass through to subsequent vattr calls */
+ vattr_context *c, /* opaque context, pass through to subsequent vattr calls */
+ Slapi_Entry *e, /* the entry that the values are for */
+ char *type, /* the type that the values belong to */
+ Slapi_Value *test_this, /* the value to compare against */
+ int* result, /* 1 for matched, 0 for not matched */
+ int flags, /* see slapi-plugin.h to support these flags */
+ void *hint /* opaque hint, pass through to subsequent vattr calls */
+ )
+{
+ int ret = SLAPI_VIRTUALATTRS_NOT_FOUND; /* assume failure */
+
+ slapi_log_error( SLAPI_LOG_TRACE, VATTRSP_PLUGIN_SUBSYSTEM,"--> vattrsp_vattr_compare\n");
+
+ /* TODO: do your thing, compare the attribute */
+
+ slapi_log_error( SLAPI_LOG_TRACE, VATTRSP_PLUGIN_SUBSYSTEM,"<-- vattrsp_vattr_compare\n");
+ return ret;
+}
+
+
+
+/*
+ * vattrsp_vattr_types
+ * -------------------
+ * vattr subsystem is requesting the types that you
+ * promise to supply values for if it calls
+ * vattrsp_vattr_get() with each type.
+ * Guesses are not good enough, the wrong answer
+ * effects what happens when all attributes
+ * are requested in the LDAP search operation.
+ * If you do not own up to an attribute,
+ * vattrsp_vattr_get() will never get called for it.
+ * If you say you will supply an attribute but
+ * vattrsp_vattr_get() does not supply a value
+ * then the attribute is returned, but *with* *no*
+ * *values*
+ *
+ * When does this get called?
+ * Only when all attributes are requested by the client
+ * and just prior to multiple calls to vattrsp_vattr_get()
+ *
+ * returns 0 on success
+ */
+int vattrsp_vattr_types(
+ vattr_sp_handle *handle, /* pass through to subsequent vattr calls */
+ Slapi_Entry *e, /* the entry that the types should have values for */
+ vattr_type_list_context *type_context, /* where we put the types */
+ int flags /* see slapi-plugin.h to support these flags */
+ )
+{
+ int ret = 0; /* assume success */
+ char *attr = "dummyAttr"; /* an attribute type that we will deliver */
+ int props = 0; /* properties of the attribute, make this SLAPI_ATTR_FLAG_OPATTR for operational attributes */
+
+ slapi_log_error( SLAPI_LOG_TRACE, VATTRSP_PLUGIN_SUBSYSTEM,"--> vattrsp_vattr_types\n");
+
+ /* TODO: for each type you will supply... */
+ if(ret)
+ {
+ /* ...do this */
+
+ /* entry contains this attr */
+ vattr_type_thang thang = {0};
+
+ thang.type_name = strcpy(thang.type_name,attr);
+ thang.type_flags = props;
+
+ /* add the type to the type context */
+ slapi_vattrspi_add_type(type_context,&thang,0);
+ }
+
+ slapi_log_error( SLAPI_LOG_TRACE, VATTRSP_PLUGIN_SUBSYSTEM,"<-- vattrsp_vattr_types\n");
+ return ret;
+}
+
+
+
diff --git a/ldap/servers/plugins/vattrsp_template/vattrsp.def b/ldap/servers/plugins/vattrsp_template/vattrsp.def
new file mode 100644
index 00000000..a0e2d8c0
--- /dev/null
+++ b/ldap/servers/plugins/vattrsp_template/vattrsp.def
@@ -0,0 +1,10 @@
+; BEGIN COPYRIGHT BLOCK
+; Copyright 2001 Sun Microsystems, Inc.
+; Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+; All rights reserved.
+; END COPYRIGHT BLOCK
+;
+DESCRIPTION 'Netscape Directory Server 6.2.1 Virtual Attribute Service Provider Template Plugin'
+EXPORTS
+ vattrsp_init @2
+ plugin_init_debug_level @3
diff --git a/ldap/servers/plugins/views/Makefile b/ldap/servers/plugins/views/Makefile
new file mode 100644
index 00000000..495c7d97
--- /dev/null
+++ b/ldap/servers/plugins/views/Makefile
@@ -0,0 +1,79 @@
+#
+# BEGIN COPYRIGHT BLOCK
+# Copyright 2001 Sun Microsystems, Inc.
+# Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+# All rights reserved.
+# END COPYRIGHT BLOCK
+#
+LDAP_SRC = ../../..
+MCOM_ROOT = ../../../../..
+
+NOSTDCLEAN=true # don't let nsconfig.mk define target clean
+NOSTDSTRIP=true # don't let nsconfig.mk define target strip
+NSPR20=true # probably should be defined somewhere else (not sure where)
+
+OBJDEST = $(OBJDIR)/lib/libviews
+LIBDIR = $(LIB_RELDIR)
+
+include $(MCOM_ROOT)/ldapserver/nsdefs.mk
+include $(MCOM_ROOT)/ldapserver/nsconfig.mk
+include $(LDAP_SRC)/nsldap.mk
+
+ifeq ($(ARCH), WINNT)
+DEF_FILE:=./views.def
+endif
+
+VIEWS_OBJS = views.o
+OBJS = $(addprefix $(OBJDEST)/, $(VIEWS_OBJS))
+
+VIEWS_DLL = views-plugin
+
+INCLUDES += -I../../slapd -I../../../include
+CFLAGS+=$(SLCFLAGS) -DSLAPD_LOGGING
+
+ifeq ($(ARCH), WINNT)
+EXTRA_LIBS_DEP += $(LIBSLAPD) $(NSPR_DEP) $(LDAPSDK_DEP)
+EXTRA_LIBS += $(NSPRLINK) $(LIBSLAPD) $(LDAP_SDK_LIBLDAP_DLL)
+VIEWS_DLL_OBJ = $(addprefix $(OBJDEST)/, dllmain.o)
+endif
+
+ifeq ($(ARCH), HPUX)
+EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAPSDK_DEP) $(NSPR_DEP) $(SECURITY_DEP)
+EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(SECURITYLINK) $(NSPRLINK) $(ICULINK)
+endif
+
+ifeq ($(ARCH), AIX)
+LD=ld
+EXTRA_LIBS_DEP += $(LIBSLAPD) $(NSPR_DEP) $(LDAPSDK_DEP)
+EXTRA_LIBS += $(LIBSLAPDLINK) $(NSPRLINK) $(LDAP_SDK_LIBLDAP_DLL)
+endif
+
+VIEWS= $(addprefix $(LIBDIR)/, $(VIEWS_DLL).$(DLL_SUFFIX))
+
+clientSDK:
+
+all: $(OBJDEST) $(LIBDIR) $(VIEWS)
+
+ifeq ($(ARCH), WINNT)
+$(VIEWS): $(OBJS) $(VIEWS_DLL_OBJ) $(DEF_FILE)
+ $(LINK_DLL) $(VIEWS_DLL_OBJ) $(EXTRA_LIBS) /DEF:$(DEF_FILE)
+else
+$(VIEWS): $(OBJS) $(VIEWS_DLL_OBJ)
+ $(LINK_DLL) $(VIEWS_DLL_OBJ) $(EXTRA_LIBS)
+endif
+
+
+veryclean: clean
+
+clean:
+ $(RM) $(OBJS)
+ifeq ($(ARCH), WINNT)
+ $(RM) $(VIEWS_DLL_OBJ)
+endif
+ $(RM) $(VIEWS)
+
+$(OBJDEST):
+ $(MKDIR) $(OBJDEST)
+
+$(LIBDIR):
+ $(MKDIR) $(LIBDIR)
diff --git a/ldap/servers/plugins/views/dllmain.c b/ldap/servers/plugins/views/dllmain.c
new file mode 100644
index 00000000..fabf8677
--- /dev/null
+++ b/ldap/servers/plugins/views/dllmain.c
@@ -0,0 +1,96 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/*
+ * Microsoft Windows specifics for BACK-LDBM DLL
+ */
+#include "ldap.h"
+#include "lber.h"
+
+
+#ifdef _WIN32
+/* Lifted from Q125688
+ * How to Port a 16-bit DLL to a Win32 DLL
+ * on the MSVC 4.0 CD
+ */
+BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved)
+{
+ WSADATA wsadata;
+
+ switch (fdwReason)
+ {
+ case DLL_PROCESS_ATTACH:
+ /* Code from LibMain inserted here. Return TRUE to keep the
+ DLL loaded or return FALSE to fail loading the DLL.
+
+ You may have to modify the code in your original LibMain to
+ account for the fact that it may be called more than once.
+ You will get one DLL_PROCESS_ATTACH for each process that
+ loads the DLL. This is different from LibMain which gets
+ called only once when the DLL is loaded. The only time this
+ is critical is when you are using shared data sections.
+ If you are using shared data sections for statically
+ allocated data, you will need to be careful to initialize it
+ only once. Check your code carefully.
+
+ Certain one-time initializations may now need to be done for
+ each process that attaches. You may also not need code from
+ your original LibMain because the operating system may now
+ be doing it for you.
+ */
+ /*
+ * 16 bit code calls UnlockData()
+ * which is mapped to UnlockSegment in windows.h
+ * in 32 bit world UnlockData is not defined anywhere
+ * UnlockSegment is mapped to GlobalUnfix in winbase.h
+ * and the docs for both UnlockSegment and GlobalUnfix say
+ * ".. function is oboslete. Segments have no meaning
+ * in the 32-bit environment". So we do nothing here.
+ */
+
+ if( errno = WSAStartup(0x0101, &wsadata ) != 0 )
+ return FALSE;
+
+ break;
+
+ case DLL_THREAD_ATTACH:
+ /* Called each time a thread is created in a process that has
+ already loaded (attached to) this DLL. Does not get called
+ for each thread that exists in the process before it loaded
+ the DLL.
+
+ Do thread-specific initialization here.
+ */
+ break;
+
+ case DLL_THREAD_DETACH:
+ /* Same as above, but called when a thread in the process
+ exits.
+
+ Do thread-specific cleanup here.
+ */
+ break;
+
+ case DLL_PROCESS_DETACH:
+ /* Code from _WEP inserted here. This code may (like the
+ LibMain) not be necessary. Check to make certain that the
+ operating system is not doing it for you.
+ */
+ WSACleanup();
+
+ break;
+ }
+ /* The return value is only used for DLL_PROCESS_ATTACH; all other
+ conditions are ignored. */
+ return TRUE; // successful DLL_PROCESS_ATTACH
+}
+#else
+int CALLBACK
+LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine )
+{
+ /*UnlockData( 0 );*/
+ return( 1 );
+}
+#endif
diff --git a/ldap/servers/plugins/views/views.c b/ldap/servers/plugins/views/views.c
new file mode 100644
index 00000000..b052167c
--- /dev/null
+++ b/ldap/servers/plugins/views/views.c
@@ -0,0 +1,1779 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* plugin which implements directory server views */
+
+#include <stdio.h>
+#include <string.h>
+#include "portable.h"
+#include "slapi-plugin.h"
+#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */
+#include "dirver.h"
+#include "statechange.h"
+#include "views.h"
+
+#include "slapi-plugin-compat4.h"
+#include "slapi-private.h"
+
+
+#define VIEW_OBJECTCLASS "nsView"
+#define VIEW_FILTER_ATTR "nsViewFilter"
+#define STATECHANGE_VIEWS_ID "Views"
+#define STATECHANGE_VIEWS_CONFG_FILTER "objectclass=" VIEW_OBJECTCLASS
+
+/* get file mode flags for unix */
+#ifndef _WIN32
+#include <sys/stat.h>
+#endif
+
+#define VIEWS_PLUGIN_SUBSYSTEM "views-plugin" /* used for logging */
+
+/* cache data structs */
+
+struct _viewLinkedList
+{
+ void *pNext;
+ void *pPrev;
+};
+typedef struct _viewLinkedList viewLinkedList;
+
+#if defined(DEBUG)
+#define _VIEW_DEBUG_FILTERS /* Turning on hurts performance */
+#endif
+
+struct _viewEntry
+{
+ viewLinkedList list;
+ char *pDn;
+ char *viewfilter; /* the raw view */
+ Slapi_Filter *includeAncestorFiltersFilter; /* the filter with all ancestor filters */
+ Slapi_Filter *excludeAllButDescendentViewsFilter; /* for building the view of views */
+ Slapi_Filter *excludeChildFiltersFilter; /* NOT all children views, for one level searches */
+ Slapi_Filter *excludeGrandChildViewsFilter; /* view filter for one level searches */
+ Slapi_Filter *includeChildViewsFilter; /* view filter for subtree searches */
+#ifdef _VIEW_DEBUG_FILTERS
+ /* monitor the cached filters with these */
+ char includeAncestorFiltersFilter_str[1024]; /* the filter with all ancestor filters */
+ char excludeAllButDescendentViewsFilter_str[1024]; /* for building the view of views */
+ char excludeChildFiltersFilter_str[1024]; /* NOT all children views, for one level searches */
+ char excludeGrandChildViewsFilter_str[1024]; /* view filter for one level searches */
+ char includeChildViewsFilter_str[1024]; /* view filter for subtree searches */
+#endif
+ char *pSearch_base; /* the parent of the top most view */
+ void *pParent;
+ void **pChildren;
+ int child_count;
+ unsigned long entryid; /* unique identifier for this entry */
+ unsigned long parentid; /* unique identifier for the parent entry */
+};
+typedef struct _viewEntry viewEntry;
+
+struct _globalViewCache
+{
+ viewEntry *pCacheViews;
+ viewEntry **ppViewIndex;
+ int cache_built;
+ int view_count;
+ PRThread *currentUpdaterThread;
+};
+typedef struct _globalViewCache golbalViewCache;
+
+static golbalViewCache theCache;
+
+/* other function prototypes */
+int views_init( Slapi_PBlock *pb );
+static int views_start( Slapi_PBlock *pb );
+static int views_close( Slapi_PBlock *pb );
+static int views_cache_create();
+static void views_update_views_cache( Slapi_Entry *e, char *dn, int modtype, Slapi_PBlock *pb, void *caller_data );
+static int views_cache_build_view_list(viewEntry **pViews);
+static int views_cache_index();
+static int views_dn_views_cb (Slapi_Entry* e, void *callback_data);
+static int views_cache_add_dn_views(char *dn, viewEntry **pViews);
+static void views_cache_add_ll_entry(void** attrval, void *theVal);
+static void views_cache_discover_parent(viewEntry *pView);
+static void views_cache_discover_children(viewEntry *pView);
+static void views_cache_discover_view_scope(viewEntry *pView);
+static void views_cache_create_applied_filter(viewEntry *pView);
+static void views_cache_create_exclusion_filter(viewEntry *pView);
+static void views_cache_create_inclusion_filter(viewEntry *pView);
+Slapi_Filter *views_cache_create_descendent_filter(viewEntry *ancestor, PRBool useID);
+static int view_search_rewrite_callback(Slapi_PBlock *pb);
+static void views_cache_backend_state_change(void *handle, char *be_name, int old_be_state, int new_be_state);
+static void views_cache_act_on_change_thread(void *arg);
+static viewEntry *views_cache_find_view(char *view);
+
+/* our api broker published api */
+static void *api[3];
+static int _internal_api_views_entry_exists(char *view_dn, Slapi_Entry *e);
+static int _internal_api_views_entry_dn_exists(char *view_dn, char *e_dn);
+static int _internal_api_views_entry_exists_general(char *view_dn, Slapi_Entry *e, char *e_dn);
+
+
+static Slapi_PluginDesc pdesc = { "views", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT,
+ "virtual directory information tree views plugin" };
+
+static void * view_plugin_identity = NULL;
+
+static PRRWLock *g_views_cache_lock;
+
+#ifdef _WIN32
+int *module_ldap_debug = 0;
+
+void plugin_init_debug_level(int *level_ptr)
+{
+ module_ldap_debug = level_ptr;
+}
+#endif
+
+/*
+** Plugin identity mgmt
+*/
+
+void view_set_plugin_identity(void * identity)
+{
+ view_plugin_identity=identity;
+}
+
+void * view_get_plugin_identity()
+{
+ return view_plugin_identity;
+}
+
+/*
+ views_init
+ --------
+ adds our callbacks to the list
+*/
+int views_init( Slapi_PBlock *pb )
+{
+ int ret = 0;
+ void * plugin_identity=NULL;
+
+ slapi_log_error( SLAPI_LOG_TRACE, VIEWS_PLUGIN_SUBSYSTEM, "--> views_init\n");
+
+ /*
+ ** Store the plugin identity for later use.
+ ** Used for internal operations
+ */
+
+ slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &plugin_identity);
+ view_set_plugin_identity(plugin_identity);
+
+ if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION,
+ SLAPI_PLUGIN_VERSION_01 ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN,
+ (void *) views_start ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN,
+ (void *) views_close ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION,
+ (void *)&pdesc ) != 0 )
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, VIEWS_PLUGIN_SUBSYSTEM,
+ "views_init: failed to register plugin\n" );
+ ret = -1;
+ }
+
+ slapi_log_error( SLAPI_LOG_TRACE, VIEWS_PLUGIN_SUBSYSTEM, "<-- views_init\n");
+ return ret;
+}
+
+void views_read_lock()
+{
+ PR_RWLock_Rlock(g_views_cache_lock);
+}
+
+void views_write_lock()
+{
+ PR_RWLock_Wlock(g_views_cache_lock);
+}
+
+void views_unlock()
+{
+ PR_RWLock_Unlock(g_views_cache_lock);
+}
+
+/*
+ views_start
+ ---------
+ This function publishes the interface for this plugin
+*/
+static int views_start( Slapi_PBlock *pb )
+{
+ int ret = 0;
+ void **statechange_api;
+
+ slapi_log_error( SLAPI_LOG_TRACE, VIEWS_PLUGIN_SUBSYSTEM, "--> views_start\n");
+
+ theCache.cache_built = 0;
+ g_views_cache_lock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "views");
+
+ /* first register our backend state change func (we'll use func pointer as handle) */
+ slapi_register_backend_state_change((void *)views_cache_backend_state_change, views_cache_backend_state_change);
+
+ /* create the view cache */
+
+ views_cache_create();
+
+ /* register callbacks for filter and search rewriting */
+ slapi_compute_add_search_rewriter(view_search_rewrite_callback);
+
+ /* register for state changes to view configuration */
+ if(!slapi_apib_get_interface(StateChange_v1_0_GUID, &statechange_api))
+ {
+ statechange_register(statechange_api, STATECHANGE_VIEWS_ID, NULL, STATECHANGE_VIEWS_CONFG_FILTER, NULL, views_update_views_cache);
+ }
+
+ /* register our api so that other subsystems can be views aware */
+ api[0] = NULL; /* reserved for api broker use */
+ api[1] = (void *)_internal_api_views_entry_exists;
+ api[2] = (void *)_internal_api_views_entry_dn_exists;
+
+ if( slapi_apib_register(Views_v1_0_GUID, api) )
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, VIEWS_PLUGIN_SUBSYSTEM, "views: failed to publish views interface\n");
+ ret = -1;
+ }
+
+ slapi_log_error( SLAPI_LOG_TRACE, VIEWS_PLUGIN_SUBSYSTEM, "<-- views_start\n");
+ return ret;
+}
+
+/* _internal_api_views_entry_exists()
+ * ----------------------------------
+ * externally published api to allow other subsystems to
+ * be views aware. Given a view and an entry, this function
+ * returns PR_TRUE if the entry would be returned by a subtree
+ * search on the view, PR_FALSE otherwise.
+ */
+static int _internal_api_views_entry_exists(char *view_dn, Slapi_Entry *e)
+{
+ return _internal_api_views_entry_exists_general(view_dn, e, NULL);
+}
+
+static int _internal_api_views_entry_dn_exists(char *view_dn, char *e_dn)
+{
+ return _internal_api_views_entry_exists_general(view_dn, NULL, e_dn);
+}
+
+static int _internal_api_views_entry_exists_general(char *view_dn, Slapi_Entry *e, char *e_dn)
+{
+ int ret = 0;
+ viewEntry *view;
+ char *dn;
+
+ /* there are two levels of scope for a view,
+ * from the parent of the view without a view filter
+ * and the parent of the top most view including a
+ * view filter - either match will do
+ */
+
+ /* find the view */
+ view = views_cache_find_view(view_dn);
+ if(0==view)
+ {
+ /* this is not the entry you are looking for */
+ goto bail;
+ }
+
+ /* normal scope - is the view an ancestor of the entry */
+ if(e_dn)
+ dn = e_dn;
+ else
+ dn = slapi_entry_get_ndn(e);
+
+ if(slapi_dn_issuffix(dn, view_dn))
+ {
+ /* this entry is physically contained in the view hiearchy */
+ ret = -1;
+ goto bail;
+ }
+
+ /* view scope - view hiearchy scope plus view filter */
+ if(slapi_dn_issuffix(dn, view->pSearch_base))
+ {
+ if(0==e)
+ {
+ Slapi_DN *sdn = slapi_sdn_new_dn_byref(dn);
+
+ slapi_search_internal_get_entry( sdn, NULL, &e , view_get_plugin_identity());
+
+ slapi_sdn_free(&sdn);
+ }
+
+ /* so far so good, apply filter */
+ if(0==slapi_filter_test_simple(e,view->includeAncestorFiltersFilter))
+ {
+ /* this entry would appear in the view */
+ ret = -1;
+ }
+ }
+
+bail:
+ return ret;
+}
+
+void views_cache_free()
+{
+ viewEntry *head = theCache.pCacheViews;
+ viewEntry *current;
+
+ slapi_log_error( SLAPI_LOG_TRACE, VIEWS_PLUGIN_SUBSYSTEM, "--> views_cache_free\n");
+
+ /* free the cache */
+ current = head;
+
+ while(current != NULL)
+ {
+ viewEntry *theView = current;
+ current = current->list.pNext;
+
+ /* free the view */
+ slapi_ch_free((void**)&theView->pDn);
+ slapi_ch_free((void**)&theView->viewfilter);
+ slapi_filter_free(theView->includeAncestorFiltersFilter,1);
+ slapi_filter_free(theView->excludeAllButDescendentViewsFilter,1);
+ slapi_filter_free(theView->excludeChildFiltersFilter,1);
+ slapi_filter_free(theView->excludeGrandChildViewsFilter,1);
+ slapi_filter_free(theView->includeChildViewsFilter,1);
+ slapi_ch_free((void**)&theView->pSearch_base);
+ slapi_ch_free((void**)&theView->pChildren);
+ slapi_ch_free((void**)&theView);
+ }
+
+ theCache.pCacheViews = NULL;
+
+ slapi_ch_free((void**)&theCache.ppViewIndex);
+
+ theCache.view_count = 0;
+
+ slapi_log_error( SLAPI_LOG_TRACE, VIEWS_PLUGIN_SUBSYSTEM, "<-- views_cache_free\n");
+}
+
+/*
+ views_close
+ ---------
+ unregisters the interface for this plugin
+*/
+static int views_close( Slapi_PBlock *pb )
+{
+ slapi_log_error( SLAPI_LOG_TRACE, VIEWS_PLUGIN_SUBSYSTEM, "--> views_close\n");
+
+ /* unregister backend state change notification */
+ slapi_unregister_backend_state_change((void *)views_cache_backend_state_change);
+
+ views_cache_free();
+
+ slapi_log_error( SLAPI_LOG_TRACE, VIEWS_PLUGIN_SUBSYSTEM, "<-- views_close\n");
+
+ return 0;
+}
+
+
+/*
+ views_cache_create
+ ---------------------
+ Walks the views in the DIT and populates the cache.
+*/
+static int views_cache_create()
+{
+ int ret = -1;
+ static int firstTime = 1;
+
+ slapi_log_error( SLAPI_LOG_TRACE, VIEWS_PLUGIN_SUBSYSTEM, "--> views_cache_create\n");
+
+
+ /* lock cache */
+ views_write_lock();
+
+ theCache.currentUpdaterThread = PR_GetCurrentThread(); /* to avoid deadlock */
+
+ if(theCache.pCacheViews)
+ {
+ /* need to get rid of the existing views */
+ views_cache_free();
+ }
+
+ /* grab the view entries */
+ ret = views_cache_build_view_list(&(theCache.pCacheViews));
+ if(!ret && theCache.pCacheViews)
+ {
+ viewEntry *head = theCache.pCacheViews;
+ viewEntry *current;
+
+ /* OK, we have a basic cache, now we need to
+ * fix up parent and children pointers
+ */
+ for(current = head; current != NULL; current = current->list.pNext)
+ {
+ views_cache_discover_parent(current);
+ views_cache_discover_children(current);
+ }
+
+ /* scope of views and cache search filters... */
+ for(current = head; current != NULL; current = current->list.pNext)
+ {
+ views_cache_discover_view_scope(current);
+ views_cache_create_applied_filter(current);
+ views_cache_create_exclusion_filter(current);
+ views_cache_create_inclusion_filter(current);
+ }
+
+ /* create the view index */
+ ret = views_cache_index();
+ if(ret != 0)
+ {
+ /* currently we cannot go on without the indexes */
+ slapi_log_error(SLAPI_LOG_FATAL, VIEWS_PLUGIN_SUBSYSTEM, "views_cache_create: failed to index cache\n");
+ }
+ else
+ theCache.cache_built = 1;
+ }
+ else
+ {
+ /* its ok to not have views to cache */
+ theCache.cache_built = 0;
+ ret = 0;
+ }
+
+ theCache.currentUpdaterThread = 0;
+
+ /* unlock cache */
+ views_unlock();
+
+ slapi_log_error( SLAPI_LOG_TRACE, VIEWS_PLUGIN_SUBSYSTEM, "<-- views_cache_create\n");
+ return ret;
+}
+
+/*
+ * views_cache_view_compare
+ * -----------------------
+ * compares the dns of two views - used for sorting the index
+ */
+int views_cache_view_compare(const void *e1, const void *e2)
+{
+ int ret;
+ Slapi_DN *dn1 = slapi_sdn_new_dn_byval((*(viewEntry**)e1)->pDn);
+ Slapi_DN *dn2 = slapi_sdn_new_dn_byval((*(viewEntry**)e2)->pDn);
+
+ ret = slapi_sdn_compare(dn1, dn2);
+
+ slapi_sdn_free(&dn1);
+ slapi_sdn_free(&dn2);
+
+ return ret;
+}
+
+/*
+ * views_cache_dn_compare
+ * -----------------------
+ * compares a dn with the dn of a view - used for searching the index
+ */
+int views_cache_dn_compare(const void *e1, const void *e2)
+{
+ int ret;
+ Slapi_DN *dn1 = slapi_sdn_new_dn_byval((char*)e1);
+ Slapi_DN *dn2 = slapi_sdn_new_dn_byval(((viewEntry*)e2)->pDn);
+
+ ret = slapi_sdn_compare(dn1, dn2);
+
+ slapi_sdn_free(&dn1);
+ slapi_sdn_free(&dn2);
+
+ return ret;
+}
+/*
+ * views_cache_index
+ * ----------------
+ * indexes the cache for fast look up of views
+ */
+static int views_cache_index()
+{
+ int ret = -1;
+ int i;
+ viewEntry *theView = theCache.pCacheViews;
+ viewEntry *current = 0;
+
+ if(theCache.ppViewIndex)
+ slapi_ch_free((void**)&theCache.ppViewIndex);
+
+ theCache.view_count = 0;
+
+ /* lets count the views */
+ for(current = theCache.pCacheViews; current != NULL; current = current->list.pNext)
+ theCache.view_count++;
+
+
+ theCache.ppViewIndex = (viewEntry**)calloc(theCache.view_count, sizeof(viewEntry*));
+ if(theCache.ppViewIndex)
+ {
+ /* copy over the views */
+ for(i=0; i<theCache.view_count; i++)
+ {
+ theCache.ppViewIndex[i] = theView;
+ theView = theView->list.pNext;
+ }
+
+ /* sort the views */
+ qsort(theCache.ppViewIndex, theCache.view_count, sizeof(viewEntry*), views_cache_view_compare);
+
+ ret = 0;
+ }
+
+ return ret;
+}
+
+
+/*
+ views_cache_view_index_bsearch - RECURSIVE
+ ----------------------------------------
+ performs a binary search on the cache view index
+ return -1 if key is not found
+*/
+viewEntry *views_cache_view_index_bsearch( const char *key, int lower, int upper )
+{
+ viewEntry *ret = 0;
+ int index = 0;
+ int compare_ret = 0;
+
+ if(upper >= lower)
+ {
+ if(upper != 0)
+ index = ((upper-lower)/2) + lower;
+ else
+ index = 0;
+
+ compare_ret = views_cache_dn_compare(key, theCache.ppViewIndex[index]);
+
+ if(!compare_ret)
+ {
+ ret = (theCache.ppViewIndex)[index];
+ }
+ else
+ {
+ /* seek elsewhere */
+ if(compare_ret < 0)
+ {
+ /* take the low road */
+ ret = views_cache_view_index_bsearch(key, lower, index-1);
+ }
+ else
+ {
+ /* go high */
+ ret = views_cache_view_index_bsearch(key, index+1, upper);
+ }
+ }
+ }
+
+ return ret;
+}
+
+
+/*
+ views_cache_find_view
+ -------------------
+ searches for a view, and if found returns it, null otherwise
+*/
+static viewEntry *views_cache_find_view(char *view)
+{
+ viewEntry *ret = 0; /* assume failure */
+
+ if(theCache.view_count != 1)
+ ret = views_cache_view_index_bsearch(view, 0, theCache.view_count-1);
+ else
+ {
+ /* only one view (that will fool our bsearch) lets check it here */
+ if(!slapi_utf8casecmp((unsigned char*)view, (unsigned char*)theCache.ppViewIndex[0]->pDn))
+ {
+ ret = theCache.ppViewIndex[0];
+ }
+ }
+
+ return ret;
+}
+
+
+/*
+ views_cache_discover_parent
+ ------------------------------
+ finds the parent of this view and caches it in view
+*/
+static void views_cache_discover_parent(viewEntry *pView)
+{
+ viewEntry *head = theCache.pCacheViews;
+ viewEntry *current;
+ int found = 0;
+
+ for(current = head; current != NULL && !found; current = current->list.pNext)
+ {
+ if(slapi_dn_isparent( current->pDn, pView->pDn ))
+ {
+ found = 1;
+ pView->pParent = current;
+ }
+ }
+
+ if(!found)
+ {
+ /* this is a top view */
+ pView->pParent = NULL;
+ }
+}
+
+
+/*
+ views_cache_discover_children
+ ------------------------------
+ finds the children of this view and caches them in view
+*/
+static void views_cache_discover_children(viewEntry *pView)
+{
+ viewEntry *head = theCache.pCacheViews;
+ viewEntry *current;
+ int child_count = 0;
+ int add_count = 0;
+
+ if(pView->pChildren)
+ {
+ slapi_ch_free((void**)&pView->pChildren);
+ pView->pChildren = NULL;
+ }
+
+ /* first lets count the children */
+ for(current = head; current != NULL; current = current->list.pNext)
+ {
+ if(slapi_dn_isparent(pView->pDn, current->pDn))
+ child_count++;
+ }
+
+ /* make the space for them */
+ pView->child_count = child_count;
+
+ pView->pChildren = calloc(child_count, sizeof(viewEntry*));
+
+ /* add them */
+ for(current = head; current != NULL; current = current->list.pNext)
+ {
+ if(slapi_dn_isparent(pView->pDn, current->pDn))
+ {
+ ((viewEntry**)pView->pChildren)[add_count] = current;
+ add_count++;
+ }
+ }
+}
+
+
+/*
+ views_cache_discover_view_scope
+ ------------------------------
+ finds the parent of the top most view and sets the scope of the view search
+*/
+
+static void views_cache_discover_view_scope(viewEntry *pView)
+{
+ viewEntry *current = pView;
+
+ if(pView->pSearch_base)
+ slapi_ch_free((void**)&pView->pSearch_base);
+
+ while(current != NULL)
+ {
+ if(current->pParent == NULL)
+ {
+ /* found top */
+ pView->pSearch_base = slapi_dn_parent(current->pDn);
+ }
+
+ current = current->pParent;
+ }
+
+}
+
+
+/*
+ views_cache_create_applied_filter
+ --------------------------------
+ builds the filters for:
+ char *includeAncestorFiltersFilter; the view with all ancestor views
+*/
+static void views_cache_create_applied_filter(viewEntry *pView)
+{
+ viewEntry *current = pView;
+ Slapi_Filter *pCurrentFilter = 0;
+ Slapi_Filter *pBuiltFilter = 0;
+ Slapi_Filter *pViewEntryExcludeFilter = 0;
+ char *buf = 0;
+ int len = 0;
+
+ if(pView->includeAncestorFiltersFilter)
+ {
+ /* release the current filter */
+ slapi_filter_free(pView->includeAncestorFiltersFilter, 1);
+ pView->includeAncestorFiltersFilter = 0;
+ }
+
+ /* create applied view filter (this view filter plus ancestors) */
+ while(current != NULL)
+ {
+ /* add this view filter to the built filter using AND */
+ char *buf;
+
+ if(!current->viewfilter)
+ {
+ current = current->pParent;
+ continue; /* skip this view */
+ }
+
+ buf = slapi_ch_strdup(current->viewfilter);
+
+ pCurrentFilter = slapi_str2filter( buf );
+ if(pBuiltFilter && pCurrentFilter)
+ pBuiltFilter = slapi_filter_join_ex( LDAP_FILTER_AND, pBuiltFilter, pCurrentFilter, 0 );
+ else
+ pBuiltFilter = pCurrentFilter;
+
+ slapi_ch_free((void **)&buf);
+
+ current = current->pParent;
+ }
+
+ /* filter for removing view entries from search */
+ pViewEntryExcludeFilter = slapi_str2filter( "(!(objectclass=" VIEW_OBJECTCLASS "))" );
+
+ if(pBuiltFilter)
+ pView->includeAncestorFiltersFilter = slapi_filter_join_ex( LDAP_FILTER_AND, pBuiltFilter, pViewEntryExcludeFilter, 0 );
+ else
+ pView->includeAncestorFiltersFilter = pViewEntryExcludeFilter;
+
+#ifdef _VIEW_DEBUG_FILTERS
+ slapi_filter_to_string(pView->includeAncestorFiltersFilter, pView->includeAncestorFiltersFilter_str, sizeof(pView->includeAncestorFiltersFilter_str));
+#endif
+}
+
+/* views_cache_create_exclusion_filter
+ * ----------------------------------
+ * makes a filter which is used for one level searches
+ * so that views show up correctly if the client filter
+ * allows: excludeGrandChildViewsFilter
+ *
+ * Also makes the filter which excludes entries which
+ * belong in descendent views: excludeChildFiltersFilter
+ */
+static void views_cache_create_exclusion_filter(viewEntry *pView)
+{
+ viewEntry *current = pView;
+ Slapi_Filter *pOrSubFilter = 0;
+ Slapi_Filter *excludeChildFiltersFilter = 0;
+ Slapi_Filter *pChildExcludeSubFilter = 0;
+ Slapi_Filter *pViewEntryExcludeFilter = 0;
+ int child_count = 0;
+ int len = 0;
+ char *buf = 0;
+ Slapi_RDN *rdn = 0;
+ char *str_rdn = 0;
+ Slapi_Filter *pCurrentFilter = 0;
+
+ /* create exclusion filter for one level searches
+ * this requires the rdns of the grandchildren of
+ * this view to be in a filter
+ */
+
+ if(pView->excludeGrandChildViewsFilter)
+ {
+ /* release the current filter */
+ slapi_filter_free(pView->excludeGrandChildViewsFilter, 1);
+ pView->excludeGrandChildViewsFilter = 0;
+ }
+
+ if(pView->excludeChildFiltersFilter)
+ {
+ /* release the current filter */
+ slapi_filter_free(pView->excludeChildFiltersFilter, 1);
+ pView->excludeChildFiltersFilter = 0;
+ }
+
+/* if(pView->child_count == 0)
+ {
+*/ /* this view has no children */
+/* pView->excludeGrandChildViewsFilter = 0;
+ pView->excludeChildFiltersFilter = 0;
+ return;
+ }
+
+
+ while(child_count < pView->child_count)
+ {
+ current = pView->pChildren[child_count];
+
+ if(current->child_count == 0)
+ {
+*/ /* no grandchildren here, skip */
+/* child_count++;
+ continue;
+ }
+*/
+ /* for each child we need to add its descendants */
+/* if(pOrSubFilter)
+ {
+ Slapi_Filter *pDescendents = views_cache_create_descendent_filter(current, TRUE);
+ if(pDescendents)
+ pOrSubFilter = slapi_filter_join_ex( LDAP_FILTER_OR, pOrSubFilter, pDescendents, 0 );
+ }
+ else
+ pOrSubFilter = views_cache_create_descendent_filter(current, TRUE);
+
+ child_count++;
+ }
+*/
+ buf=PR_smprintf("(parentid=%lu)", pView->entryid);
+ pView->excludeGrandChildViewsFilter = slapi_str2filter( buf );
+ PR_smprintf_free(buf);
+
+/* if(pOrSubFilter)
+ pView->excludeGrandChildViewsFilter = slapi_filter_join_ex( LDAP_FILTER_NOT, pOrSubFilter, NULL, 0 );*/
+
+ excludeChildFiltersFilter = views_cache_create_descendent_filter(pView, PR_FALSE);
+ if(excludeChildFiltersFilter)
+ pView->excludeChildFiltersFilter = slapi_filter_join_ex( LDAP_FILTER_NOT, excludeChildFiltersFilter, NULL, 0 );
+
+#ifdef _VIEW_DEBUG_FILTERS
+ slapi_filter_to_string(pView->excludeGrandChildViewsFilter, pView->excludeGrandChildViewsFilter_str, sizeof(pView->excludeGrandChildViewsFilter_str));
+ slapi_filter_to_string(pView->excludeChildFiltersFilter, pView->excludeChildFiltersFilter_str, sizeof(pView->excludeChildFiltersFilter_str));
+#endif
+}
+
+
+Slapi_Filter *views_cache_create_descendent_filter(viewEntry *ancestor, PRBool useEntryID)
+{
+ int child_count = 0;
+ Slapi_Filter *pOrSubFilter = 0;
+
+ while(child_count < ancestor->child_count)
+ {
+ Slapi_Filter *pDescendentSubFilter = 0;
+ Slapi_RDN *rdn = 0;
+ char *str_rdn = 0;
+ Slapi_Filter *pCurrentFilter = 0;
+ viewEntry *currentChild = ancestor->pChildren[child_count];
+ char *buf = 0;
+ int len = 0;
+
+ /* for each child we need to add its descendants
+ * we do this now before processing this view
+ * to try to help the filter code out by having
+ * the most significant filters first
+ */
+ pDescendentSubFilter = views_cache_create_descendent_filter(currentChild, useEntryID);
+ if(pDescendentSubFilter)
+ if(pOrSubFilter)
+ pOrSubFilter = slapi_filter_join_ex( LDAP_FILTER_OR, pOrSubFilter, pDescendentSubFilter, 0 );
+ else
+ pOrSubFilter = pDescendentSubFilter;
+
+ if(useEntryID)
+ {
+ /* we need the RDN of this child */
+/* rdn = slapi_rdn_new_dn(currentChild->pDn);
+ str_rdn = (char *)slapi_rdn_get_rdn(rdn);
+ len = strlen(str_rdn);
+
+ buf=PR_smprintf("(%s)", str_rdn);*/
+
+ /* uniquely identify this child */
+ buf=PR_smprintf("(parentid=%lu)", currentChild->entryid);
+ }
+ else
+ {
+ /* this is a filter based filter */
+ if(currentChild->viewfilter)
+ {
+ buf=PR_smprintf("%s",currentChild->viewfilter);
+ }
+ }
+
+ if(buf)
+ {
+ pCurrentFilter = slapi_str2filter( buf );
+ if(pOrSubFilter)
+ pOrSubFilter = slapi_filter_join_ex( LDAP_FILTER_OR, pOrSubFilter, pCurrentFilter, 0 );
+ else
+ pOrSubFilter = pCurrentFilter;
+
+ PR_smprintf_free(buf);
+ }
+
+
+ child_count++;
+ }
+
+ return pOrSubFilter;
+}
+
+
+/* views_cache_create_inclusion_filter
+ * ----------------------------------
+ * makes a filter which is used for subtree searches
+ * so that views show up correctly if the client filter
+ * allows
+ */
+static void views_cache_create_inclusion_filter(viewEntry *pView)
+{
+ viewEntry *head = theCache.pCacheViews;
+/* viewEntry *current; */
+/* Slapi_Filter *view_filter; */
+ char *view_filter_str;
+
+ if(pView->includeChildViewsFilter)
+ {
+ /* release the current filter */
+ slapi_filter_free(pView->includeChildViewsFilter, 1);
+ pView->includeChildViewsFilter = 0;
+ }
+#if 0
+ for(current = head; current != NULL; current = current->list.pNext)
+ {
+ Slapi_DN *viewDN;
+ Slapi_RDN *viewRDN;
+ char *viewRDNstr;
+ char *buf = 0;
+ Slapi_Filter *viewSubFilter;
+
+ /* if this is this a descendent, ignore it */
+ if(slapi_dn_issuffix(current->pDn,pView->pDn) && !(current == pView))
+ continue;
+
+ viewDN = slapi_sdn_new_dn_byref(current->pDn);
+ viewRDN = slapi_rdn_new();
+
+ slapi_sdn_get_rdn(viewDN,viewRDN);
+ viewRDNstr = (char *)slapi_rdn_get_rdn(viewRDN);
+
+ buf = calloc(1, strlen(viewRDNstr) + 11 ); /* 3 for filter */
+ sprintf(buf, "(%s)", viewRDNstr );
+ viewSubFilter = slapi_str2filter( buf );
+
+ if(pView->includeChildViewsFilter)
+ pView->includeChildViewsFilter = slapi_filter_join_ex( LDAP_FILTER_OR, pView->includeChildViewsFilter, viewSubFilter, 0 );
+ else
+ pView->includeChildViewsFilter = viewSubFilter;
+
+ slapi_ch_free((void **)&buf);
+ slapi_sdn_free(&viewDN);
+ slapi_rdn_free(&viewRDN);
+
+ child_count++;
+ }
+#endif
+
+ /* exclude all other view entries but decendents */
+/* pView->includeChildViewsFilter = slapi_filter_join_ex( LDAP_FILTER_NOT, pView->includeChildViewsFilter, NULL, 0 );
+*/
+ /* it seems reasonable to include entries which
+ * may not fit the view decription but which
+ * are actually *contained* in the view
+ * therefore we use parentids for the view
+ * filter
+ */
+
+
+ /* add decendents */
+ pView->includeChildViewsFilter = views_cache_create_descendent_filter(pView, PR_TRUE);
+
+ /* add this view */
+ view_filter_str = PR_smprintf("(parentid=%lu)", pView->entryid);
+
+ if(pView->includeChildViewsFilter)
+ {
+ pView->includeChildViewsFilter = slapi_filter_join_ex( LDAP_FILTER_OR, slapi_str2filter( view_filter_str ), pView->includeChildViewsFilter, PR_FALSE);
+ }
+ else
+ {
+ pView->includeChildViewsFilter = slapi_str2filter( view_filter_str );
+ }
+ PR_smprintf_free(view_filter_str);
+ view_filter_str = NULL;
+
+ /* and make sure the this applies only to views */
+
+/* if(pView->includeChildViewsFilter)
+ {*/
+/* Not necessary since we now use entryid in the filter,
+ so all will be views anyway, and the less sub-filters
+ the better
+ view_filter_str = strdup("(objectclass=" VIEW_OBJECTCLASS ")");
+ view_filter = slapi_str2filter( view_filter_str );
+*/
+ /* child views first because entryid indexed
+ * and makes evaluation faster when a bunch
+ * of indexed filter evaluations with only one
+ * target are evaluated first rather than an
+ * indexed filter which will provide many entries
+ * that may trigger an index evaluation short
+ * circuit. i.e. if one of the child filters is
+ * true then we have one entry, if not, then we
+ * have used indexes completely to determine that
+ * no entry matches and (objectclass=nsview) is never
+ * evaluated.
+ * I should imagine this will hold for all but the
+ * very deepest, widest view trees when subtree
+ * searches are performed from the top
+ */
+/* pView->includeChildViewsFilter = slapi_filter_join_ex( LDAP_FILTER_AND, pView->includeChildViewsFilter, view_filter, 0 );
+ }
+ else
+ {
+ view_filter_str = strdup("(objectclass=nsviewincludenone)"); *//* hackery to get the right result */
+/* pView->includeChildViewsFilter = slapi_str2filter( view_filter_str );
+ }
+*/
+#ifdef _VIEW_DEBUG_FILTERS
+ slapi_filter_to_string(pView->includeChildViewsFilter, pView->includeChildViewsFilter_str, sizeof(pView->includeChildViewsFilter_str));
+#endif
+}
+
+
+/*
+ views_cache_build_view_list
+ -------------------------------
+ builds the list of views by searching for them throughout the DIT
+*/
+static int views_cache_build_view_list(viewEntry **pViews)
+{
+ int ret = 0;
+ Slapi_PBlock *pSuffixSearch = 0;
+ Slapi_Entry **pSuffixList = 0;
+ Slapi_Attr *suffixAttr;
+ struct berval **suffixVals;
+ char *attrType = 0;
+ char *attrs[2];
+ int suffixIndex = 0;
+ int valIndex = 0;
+ int cos_def_available = 0;
+ static int firstTime = 1;
+
+ slapi_log_error(SLAPI_LOG_TRACE, VIEWS_PLUGIN_SUBSYSTEM, "--> views_cache_build_view_list\n");
+
+ /*
+ the views may be anywhere in the DIT,
+ so our first task is to find them.
+ */
+
+ attrs[0] = "namingcontexts";
+ attrs[1] = 0;
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, VIEWS_PLUGIN_SUBSYSTEM, "views: Building view cache.\n");
+
+ pSuffixSearch = slapi_search_internal("",LDAP_SCOPE_BASE,"(objectclass=*)",NULL,attrs,0);
+ if(pSuffixSearch)
+ slapi_pblock_get( pSuffixSearch, SLAPI_PLUGIN_INTOP_RESULT, &ret);
+
+ if(pSuffixSearch && ret == LDAP_SUCCESS)
+ {
+ /* iterate through the suffixes and search for views */
+ slapi_pblock_get( pSuffixSearch, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &pSuffixList);
+ if(pSuffixList)
+ {
+ while(pSuffixList[suffixIndex])
+ {
+ if(!slapi_entry_first_attr(pSuffixList[suffixIndex], &suffixAttr))
+ {
+ do
+ {
+ attrType = 0;
+ slapi_attr_get_type(suffixAttr, &attrType);
+ if(attrType && !slapi_utf8casecmp((unsigned char*)attrType, (unsigned char*)"namingcontexts"))
+ {
+ if(!slapi_attr_get_bervals_copy(suffixAttr, &suffixVals))
+ {
+ valIndex = 0;
+
+ if(suffixVals)
+ {
+ while(suffixVals[valIndex])
+ {
+ /* here's a suffix, lets search it... */
+ if(suffixVals[valIndex]->bv_val)
+ views_cache_add_dn_views(suffixVals[valIndex]->bv_val ,pViews);
+
+ valIndex++;
+ }
+
+
+ ber_bvecfree( suffixVals );
+ suffixVals = NULL;
+ }
+ }
+ }
+
+ } while(!slapi_entry_next_attr(pSuffixList[suffixIndex], suffixAttr, &suffixAttr));
+ }
+ suffixIndex++;
+ }
+ }
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_PLUGIN, VIEWS_PLUGIN_SUBSYSTEM, "views_cache_build_view_list: failed to find suffixes\n");
+ ret = -1;
+ }
+
+ /* clean up */
+ if(pSuffixSearch)
+ {
+ slapi_free_search_results_internal(pSuffixSearch);
+ slapi_pblock_destroy(pSuffixSearch);
+ }
+
+
+ slapi_log_error(SLAPI_LOG_TRACE, VIEWS_PLUGIN_SUBSYSTEM, "<-- views_cache_build_view_list\n");
+ return ret;
+}
+
+/* struct to support search callback API */
+struct dn_views_info {
+ viewEntry **pViews;
+ int ret;
+};
+
+/* does same funcationality as views_add_dn_views except it is invoked via a callback */
+
+static int views_dn_views_cb (Slapi_Entry* e, void *callback_data) {
+ struct dn_views_info *info;
+ char *filter = 0;
+ char *pDn = 0;
+ struct berval **dnVals;
+ Slapi_Attr *dnAttr;
+ char *attrType = 0;
+ char *attrs[3];
+ viewEntry *pView;
+
+ attrs[0] = VIEW_FILTER_ATTR;
+ attrs[1] = "entryid";
+ attrs[2] = 0;
+ info=(struct dn_views_info *)callback_data;
+
+ info->ret = 0;
+
+ pDn = slapi_entry_get_ndn(e);
+
+ /* create the view */
+ pView = calloc(1, sizeof(viewEntry));
+ pView->pDn = slapi_ch_strdup(pDn);
+
+ if(!slapi_entry_first_attr(e, &dnAttr))
+ {
+ do
+ {
+ attrType = 0;
+
+
+ /* get the filter */
+ slapi_attr_get_type(dnAttr, &attrType);
+ if(attrType && !strcasecmp(attrType,VIEW_FILTER_ATTR))
+ {
+ if(!slapi_attr_get_bervals_copy(dnAttr, &dnVals))
+ {
+ /* add filter */
+ pView->viewfilter = slapi_ch_strdup(dnVals[0]->bv_val);
+ }
+
+ ber_bvecfree( dnVals );
+ dnVals = NULL;
+ }
+
+ if(attrType && !strcasecmp(attrType,"entryid"))
+ {
+ Slapi_Value *val = 0;
+
+ slapi_attr_first_value(dnAttr, &val);
+ pView->entryid = slapi_value_get_ulong(val);
+ }
+
+ if(attrType && !strcasecmp(attrType,"parentid"))
+ {
+ Slapi_Value *val = 0;
+
+ slapi_attr_first_value(dnAttr, &val);
+ pView->parentid = slapi_value_get_ulong(val);
+ }
+
+ } while(!slapi_entry_next_attr(e, dnAttr, &dnAttr));
+
+ }
+
+ /* add view to the cache */
+ views_cache_add_ll_entry((void**)info->pViews, (void *)pView);
+
+ return info->ret;
+}
+
+
+/*
+ views_cache_add_dn_views
+ -------------------------
+ takes a dn as argument and searches the dn for views,
+ adding any found to the view cache. Change to use search callback API
+*/
+
+#define DN_VIEW_FILTER "(objectclass=" VIEW_OBJECTCLASS ")"
+
+static int views_cache_add_dn_views(char *dn, viewEntry **pViews)
+{
+ Slapi_PBlock *pDnSearch = 0;
+ struct dn_views_info info;
+ pDnSearch = slapi_pblock_new();
+ if (pDnSearch) {
+ info.ret=-1;
+ info.pViews=pViews;
+ slapi_search_internal_set_pb(pDnSearch, dn, LDAP_SCOPE_SUBTREE,
+ DN_VIEW_FILTER,NULL,0,
+ NULL,NULL,view_get_plugin_identity(),0);
+ slapi_search_internal_callback_pb(pDnSearch,
+ &info /* callback_data */,
+ NULL/* result_callback */,
+ views_dn_views_cb,
+ NULL /* referral_callback */);
+ slapi_pblock_destroy (pDnSearch);
+ }
+ return info.ret;
+}
+
+/*
+ views_cache_add_ll_entry
+ ---------------------------------------------------
+ the element is added to the head of the linked list
+
+ *NOTE* this function assumes and *requires* that the structures
+ passed to it in "attrval" and "theVal" have a viewLinkedList
+ member, and it is the *first* member of the structure. This
+ is safe because this is a module level function, and all functions
+ which call this one are part of the same sub-system.
+*/
+static void views_cache_add_ll_entry(void** attrval, void *theVal)
+{
+ slapi_log_error(SLAPI_LOG_TRACE, VIEWS_PLUGIN_SUBSYSTEM, "--> views_cache_add_ll_entry\n");
+
+ if(*attrval)
+ {
+ /* push this to the start of the list (because its quick) */
+ ((viewLinkedList*)theVal)->pNext = *attrval;
+ ((viewLinkedList*)(*attrval))->pPrev = theVal;
+ *attrval = theVal;
+ }
+ else
+ {
+ /* new or end of list */
+ ((viewLinkedList*)theVal)->pNext = NULL;
+ ((viewLinkedList*)theVal)->pPrev = NULL;
+ *attrval = theVal;
+ }
+
+ slapi_log_error(SLAPI_LOG_TRACE, VIEWS_PLUGIN_SUBSYSTEM, "<-- views_cache_add_ll_entry\n");
+}
+
+
+/*
+ views_update_views_cache
+ -----------------------
+
+ update internal view cache after state change
+*/
+static void views_update_views_cache( Slapi_Entry *e, char *dn, int modtype, Slapi_PBlock *pb, void *caller_data )
+{
+ char *pDn;
+ viewEntry *theView;
+ viewEntry *current;
+ Slapi_Attr *attr;
+ struct berval val;
+ int build_cache = 0;
+
+ slapi_log_error( SLAPI_LOG_TRACE, VIEWS_PLUGIN_SUBSYSTEM, "--> views_update_views_cache\n");
+
+ views_write_lock();
+
+ if(!theCache.cache_built)
+ {
+ /* zarro views = no cache,
+ * this is probably an add op
+ * lets build the cache
+ */
+ build_cache = 1;
+ goto unlock_cache;
+ }
+
+ pDn = slapi_entry_get_ndn(e);
+ theView = views_cache_find_view(pDn);
+
+ switch(modtype)
+ {
+ case LDAP_CHANGETYPE_MODIFY:
+ /* if still a view and exists
+ * update string filter
+ * update the filters of all views
+ * if just became a view fall through to add op
+ * if stopped being a view fall through to delete op
+ */
+
+ /* determine what happenned - does the view exist currently? */
+ if(theView)
+ {
+ /* does it have the view objectclass? */
+ if(!slapi_entry_attr_find( e, "objectclass", &attr ))
+ {
+ val.bv_len = 8;
+ val.bv_val = VIEW_OBJECTCLASS;
+
+ if(!slapi_attr_value_find( attr, &val))
+ {
+ /* it is a view */
+ attr = 0;
+
+ /* has the filter changed? */
+ slapi_entry_attr_find( e, VIEW_FILTER_ATTR, &attr );
+
+ if(attr)
+ {
+ if(theView->viewfilter) /* NULL means a filter added */
+ {
+ /* we could translate the string filter into
+ * a real filter and compare against
+ * the view - that would tell us if the filter
+ * was substantively changed.
+ *
+ * But we're not gonna do that :)
+ */
+ val.bv_len = strlen(theView->viewfilter)+1;
+ val.bv_val = theView->viewfilter;
+
+ if(!slapi_attr_value_find( attr, &val))
+ {
+ /* filter unchanged */
+ break;
+ }
+ }
+ }
+ else
+ {
+ /* if no filter in view, then no change */
+ if(theView->viewfilter == 0)
+ break;
+ }
+
+ /* this was indeed a significant mod, add the new filter */
+ if(theView->viewfilter)
+ slapi_ch_free((void**)&theView->viewfilter);
+
+ if(attr)
+ {
+ Slapi_Value *v;
+ slapi_attr_first_value( attr, &v );
+ theView->viewfilter = slapi_ch_strdup(slapi_value_get_string(v));
+ }
+
+ /* update all filters */
+ for(current = theCache.pCacheViews; current != NULL; current = current->list.pNext)
+ {
+ views_cache_create_applied_filter(current);
+ views_cache_create_exclusion_filter(current);
+ views_cache_create_inclusion_filter(current);
+ }
+ }
+ else
+ {
+ /* this is a delete operation */
+ modtype = LDAP_CHANGETYPE_DELETE;
+ }
+ }
+ else
+ /* thats bad */
+ break;
+ }
+ else
+ {
+ /* this is an add operation */
+ modtype = LDAP_CHANGETYPE_ADD;
+ }
+
+ case LDAP_CHANGETYPE_DELETE:
+ /* remove view entry from list
+ * update children of parent
+ * update all child filters
+ * re-index
+ */
+ if(modtype == LDAP_CHANGETYPE_DELETE)
+ {
+
+ if(theCache.view_count-1)
+ {
+ /* detach view */
+ if(theView->list.pPrev)
+ ((viewEntry*)(theView->list.pPrev))->list.pNext = theView->list.pNext;
+
+ if(theView->list.pNext)
+ {
+ ((viewEntry*)(theView->list.pNext))->list.pPrev = theView->list.pPrev;
+
+ if(theView->list.pPrev == NULL) /* if this is the head */
+ theCache.pCacheViews = (viewEntry*)(theView->list.pNext);
+ }
+
+ /* update children */
+ if(theView->pParent)
+ views_cache_discover_children((viewEntry*)theView->pParent);
+
+ /* update filters */
+ for(current = theCache.pCacheViews; current != NULL; current = current->list.pNext)
+ {
+ views_cache_create_applied_filter(current);
+ views_cache_create_exclusion_filter(current);
+ views_cache_create_inclusion_filter(current);
+ }
+
+ /* reindex */
+ views_cache_index();
+ }
+ else
+ {
+ theCache.pCacheViews = NULL;
+ theCache.view_count = 0;
+ theCache.cache_built = 0;
+ }
+
+ /* free the view */
+ slapi_ch_free((void**)&theView->pDn);
+ slapi_ch_free((void**)&theView->viewfilter);
+ slapi_filter_free(theView->includeAncestorFiltersFilter,1);
+ slapi_filter_free(theView->excludeAllButDescendentViewsFilter,1);
+ slapi_filter_free(theView->excludeChildFiltersFilter,1);
+ slapi_filter_free(theView->excludeGrandChildViewsFilter,1);
+ slapi_filter_free(theView->includeChildViewsFilter,1);
+ slapi_ch_free((void**)&theView->pSearch_base);
+ slapi_ch_free((void**)&theView->pChildren);
+ slapi_ch_free((void**)&theView);
+
+ break;
+ }
+
+ case LDAP_CHANGETYPE_ADD:
+ /* create view entry
+ * add it to list
+ * update children of parent
+ * update all child filters
+ * re-index
+ */
+ if(modtype == LDAP_CHANGETYPE_ADD)
+ {
+ theView = calloc(1, sizeof(viewEntry));
+ theView->pDn = slapi_ch_strdup(pDn);
+
+ /* get the view filter, the entryid, and the parentid */
+ slapi_entry_attr_find( e, VIEW_FILTER_ATTR, &attr );
+
+ if(attr)
+ {
+ Slapi_Value *v;
+ slapi_attr_first_value( attr, &v );
+ theView->viewfilter = slapi_ch_strdup(slapi_value_get_string(v));
+ }
+ else
+ theView->viewfilter = NULL;
+
+ slapi_entry_attr_find( e, "entryid", &attr );
+
+ if(attr)
+ {
+ Slapi_Value *v;
+ slapi_attr_first_value( attr, &v );
+ theView->entryid = slapi_value_get_ulong(v);
+ }
+ else
+ theView->entryid = 0;
+
+ slapi_entry_attr_find( e, "parentid", &attr );
+
+ if(attr)
+ {
+ Slapi_Value *v;
+ slapi_attr_first_value( attr, &v );
+ theView->parentid = slapi_value_get_ulong(v);
+ }
+ else
+ theView->parentid = 0;
+
+ /* add view to the cache */
+ views_cache_add_ll_entry((void**)theCache.pCacheViews, (void *)theView);
+
+ views_cache_discover_parent(theView);
+ if(theView->pParent)
+ views_cache_discover_children((viewEntry*)theView->pParent);
+
+ /* update filters */
+ for(current = theCache.pCacheViews; current != NULL; current = current->list.pNext)
+ {
+ views_cache_discover_view_scope(current); /* if ns-view oc added, new view may be top */
+ views_cache_create_applied_filter(current);
+ views_cache_create_exclusion_filter(current);
+ views_cache_create_inclusion_filter(current);
+ }
+
+ /* reindex */
+ views_cache_index();
+ break;
+ }
+
+ case LDAP_CHANGETYPE_MODDN:
+ /* get old dn to find the view
+ * change dn
+ * update parents and children
+ * update all filters
+ * reindex
+ */
+
+ {
+ char *old_dn;
+ Slapi_Entry *old_entry;
+
+ slapi_pblock_get( pb, SLAPI_ENTRY_PRE_OP, &old_entry );
+ old_dn = slapi_entry_get_ndn(old_entry);
+
+ theView = views_cache_find_view(old_dn);
+ if(theView)
+ {
+ slapi_ch_free((void**)&theView->pDn);
+ theView->pDn = slapi_ch_strdup(pDn);
+
+ for(current = theCache.pCacheViews; current != NULL; current = current->list.pNext)
+ {
+ views_cache_discover_parent(current);
+ views_cache_discover_children(current);
+ }
+
+ for(current = theCache.pCacheViews; current != NULL; current = current->list.pNext)
+ {
+ views_cache_discover_view_scope(current);
+ views_cache_create_applied_filter(current);
+ views_cache_create_exclusion_filter(current);
+ views_cache_create_inclusion_filter(current);
+ }
+ }
+ /* reindex */
+ views_cache_index();
+ break;
+ }
+
+ default:
+ /* we don't care about this op */
+ break;
+ }
+
+unlock_cache:
+ views_unlock();
+
+ if(build_cache)
+ {
+ views_cache_create();
+ }
+
+ slapi_log_error( SLAPI_LOG_TRACE, VIEWS_PLUGIN_SUBSYSTEM, "<-- views_update_views_cache\n");
+}
+
+
+
+/*
+ * view_search_rewrite_callback
+ * ----------------------------
+ * this is the business end of the plugin
+ * this function is called from slapd
+ * rewrites the search to conform to the view
+ * Meaning of the return code :
+ * -1 : keep looking
+ * 0 : rewrote OK
+ * 1 : refuse to do this search
+ * 2 : operations error
+ */
+static int view_search_rewrite_callback(Slapi_PBlock *pb)
+{
+ int ret = -1;
+ char *base = 0;
+ Slapi_Filter *clientFilter = 0;
+ Slapi_Filter *includeAncestorFiltersFilter = 0; /* the view with all ancestor views */
+ Slapi_Filter *excludeChildFiltersFilter = 0; /* NOT all children views, for one level searches */
+ Slapi_Filter *excludeGrandChildViewsFilter = 0; /* view filter for one level searches */
+ Slapi_Filter *includeChildViewsFilter = 0; /* view filter for subtree searches */
+ Slapi_Filter *seeViewsFilter = 0; /* view filter to see views */
+ Slapi_Filter *outFilter = 0;
+ int scope = 0;
+ int set_scope = LDAP_SCOPE_SUBTREE;
+ viewEntry *theView = 0;
+
+#ifdef _VIEW_DEBUG_FILTERS
+ char outFilter_str[1024];
+ char clientFilter_str[1024];
+ char includeAncestorFiltersFilter_str[1024];
+ char excludeChildFiltersFilter_str[1024];
+ char excludeGrandChildViewsFilter_str[1024];
+ char includeChildViewsFilter_str[1024];
+#endif
+
+ /* if no cache, no views */
+ if(!theCache.cache_built)
+ goto end;
+
+ /* avoid locking if this thread is the updater */
+ if(theCache.currentUpdaterThread)
+ {
+ PRThread *thisThread = PR_GetCurrentThread();
+ if(thisThread == theCache.currentUpdaterThread)
+ goto end;
+ }
+
+ /* first, find out if this is a base search (we do nothing) */
+ slapi_pblock_get(pb, SLAPI_SEARCH_SCOPE, &scope);
+ if(scope == LDAP_SCOPE_BASE)
+ goto end;
+
+ /* if base of the search is a view */
+ slapi_pblock_get(pb, SLAPI_SEARCH_TARGET, &base);
+
+ /* Read lock the cache */
+ views_read_lock();
+
+ theView = views_cache_find_view(base);
+
+ /* if the view is disabled (we service subtree searches in this case) */
+ if(!theView || !theView->viewfilter && scope == LDAP_SCOPE_ONELEVEL)
+ {
+ /* unlock the cache */
+ views_unlock();
+ goto end;
+ }
+
+
+ /* this is a view search, and we are smokin' */
+
+ /* grab the view filters we are going to need now so we can release the cache lock */
+ if(scope == LDAP_SCOPE_ONELEVEL)
+ {
+ excludeChildFiltersFilter = slapi_filter_dup(theView->excludeChildFiltersFilter);
+ excludeGrandChildViewsFilter = slapi_filter_dup(theView->excludeGrandChildViewsFilter);
+
+#ifdef _VIEW_DEBUG_FILTERS
+ slapi_filter_to_string(excludeChildFiltersFilter, excludeChildFiltersFilter_str, sizeof(excludeChildFiltersFilter_str));
+ slapi_filter_to_string(excludeGrandChildViewsFilter, excludeGrandChildViewsFilter_str, sizeof(excludeGrandChildViewsFilter_str));
+#endif
+
+ }
+
+ includeChildViewsFilter = slapi_filter_dup(theView->includeChildViewsFilter);
+
+#ifdef _VIEW_DEBUG_FILTERS
+ slapi_filter_to_string(includeChildViewsFilter, includeChildViewsFilter_str, sizeof(includeChildViewsFilter_str));
+#endif
+
+ /* always used */
+ includeAncestorFiltersFilter = slapi_filter_dup(theView->includeAncestorFiltersFilter);
+
+#ifdef _VIEW_DEBUG_FILTERS
+ slapi_filter_to_string(includeAncestorFiltersFilter, includeAncestorFiltersFilter_str, sizeof(includeAncestorFiltersFilter_str));
+#endif
+
+ /* unlock the cache */
+ views_unlock();
+
+ /* rewrite search scope and base*/
+ slapi_pblock_set(pb, SLAPI_SEARCH_SCOPE, &set_scope);
+
+ base = slapi_ch_strdup(theView->pSearch_base);
+ slapi_pblock_set(pb, SLAPI_SEARCH_TARGET, base);
+
+ /* concatenate the filters */
+
+ /* grab the client filter - we need 2 copies */
+ slapi_pblock_get(pb, SLAPI_SEARCH_FILTER, &clientFilter);
+
+#ifdef _VIEW_DEBUG_FILTERS
+ slapi_filter_to_string(clientFilter, clientFilter_str, sizeof(clientFilter_str));
+#endif
+
+ /* client supplied filter AND inclusion filter - make sure we can see views */
+ if(scope == LDAP_SCOPE_ONELEVEL)
+ {
+ Slapi_Filter *clientSeeViewsFilter = 0; /* view filter to see views */
+
+ clientSeeViewsFilter = slapi_filter_dup(clientFilter);
+ if(excludeGrandChildViewsFilter)
+ seeViewsFilter = slapi_filter_join_ex( LDAP_FILTER_AND, excludeGrandChildViewsFilter, clientSeeViewsFilter, 0 );
+ else
+ seeViewsFilter = clientSeeViewsFilter;
+ }
+
+ /* this filter is to lock our view to the subtree at hand */
+ if(seeViewsFilter && includeChildViewsFilter)
+ seeViewsFilter = slapi_filter_join_ex( LDAP_FILTER_AND, includeChildViewsFilter, seeViewsFilter, 0 );
+ else
+ {
+ if(includeChildViewsFilter)
+ seeViewsFilter = includeChildViewsFilter;
+ }
+
+ /* create target filter */
+ if(includeAncestorFiltersFilter)
+ outFilter = slapi_filter_join_ex( LDAP_FILTER_AND, includeAncestorFiltersFilter, clientFilter, 0 );
+ else
+ outFilter = clientFilter;
+
+ if(scope == LDAP_SCOPE_ONELEVEL)
+ {
+ if(excludeChildFiltersFilter)
+ outFilter = slapi_filter_join_ex( LDAP_FILTER_AND, outFilter, excludeChildFiltersFilter, 0 );
+ }
+
+ if(seeViewsFilter)
+ outFilter = slapi_filter_join_ex( LDAP_FILTER_OR, outFilter, seeViewsFilter, 0 );
+
+#ifdef _VIEW_DEBUG_FILTERS
+ slapi_filter_to_string(outFilter, outFilter_str, sizeof(outFilter_str));
+#endif
+
+ /* make it happen */
+ slapi_pblock_set(pb, SLAPI_SEARCH_FILTER, outFilter);
+
+ ret = -2;
+
+end:
+ return ret;
+}
+
+/*
+ * views_cache_backend_state_change()
+ * --------------------------------
+ * This is called when a backend changes state
+ * We simply signal to rebuild the cache in this case
+ *
+ */
+static void views_cache_backend_state_change(void *handle, char *be_name,
+ int old_be_state, int new_be_state)
+{
+ /* we will create a thread to do this since
+ * calling views_cache_create() directly will
+ * hold up the op
+ */
+ if ((PR_CreateThread (PR_USER_THREAD,
+ views_cache_act_on_change_thread,
+ NULL,
+ PR_PRIORITY_NORMAL,
+ PR_GLOBAL_THREAD,
+ PR_UNJOINABLE_THREAD,
+ SLAPD_DEFAULT_THREAD_STACKSIZE)) == NULL )
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, VIEWS_PLUGIN_SUBSYSTEM,
+ "views_cache_backend_state_change: PR_CreateThread failed\n" );
+ }
+}
+
+static void views_cache_act_on_change_thread(void *arg)
+{
+ views_cache_create();
+}
diff --git a/ldap/servers/plugins/views/views.def b/ldap/servers/plugins/views/views.def
new file mode 100644
index 00000000..26d28966
--- /dev/null
+++ b/ldap/servers/plugins/views/views.def
@@ -0,0 +1,10 @@
+; BEGIN COPYRIGHT BLOCK
+; Copyright 2001 Sun Microsystems, Inc.
+; Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+; All rights reserved.
+; END COPYRIGHT BLOCK
+;
+DESCRIPTION 'Netscape Directory Server 7.0 State Change Plugin'
+EXPORTS
+ views_init @2
+ plugin_init_debug_level @3